Ускорение выполнения задачи
От: Аноним  
Дата: 17.07.09 07:00
Оценка:
В базе есть миллион клиентов, для каждого клиента нужно сгенерировать отчет и сохранить обратно в базу.
Сейчас на эту задачу уходит 5 часов если выполнять последовательно.
Попробовал через пул-потокв (10 потоков) — заняло 4,5 часов.
Согласно бизнес требованиям задача должна выполнятся не более 1 часа.


Приложение использует Spring + Hibernate и одну базу данных. Потяно что потоки конкурируют за доступ к данным, но все равно мне непонятно почему 10 потоков не дали значительного прироста производительности.

Какие еще могут быть варианты.
Re: Ускорение выполнения задачи
От: Blazkowicz Россия  
Дата: 17.07.09 07:06
Оценка:
Здравствуйте, Аноним, Вы писали:

А>В базе есть миллион клиентов, для каждого клиента нужно сгенерировать отчет и сохранить обратно в базу.

А>Сейчас на эту задачу уходит 5 часов если выполнять последовательно.
А>Попробовал через пул-потокв (10 потоков) — заняло 4,5 часов.
А>Согласно бизнес требованиям задача должна выполнятся не более 1 часа.
Таки поставить профайлер и посмотреть на какие этапы уходить больше процессорного времени.

А>Приложение использует Spring + Hibernate и одну базу данных. Потяно что потоки конкурируют за доступ к данным,

Потоки конкурируют на чтение??? Надеюсь вы отчеты без Hibernate строите?

А>но все равно мне непонятно почему 10 потоков не дали значительного прироста производительности.

Возможно потому что узкое место не в CPU?
Re: Ускорение выполнения задачи
От: tavr  
Дата: 17.07.09 07:59
Оценка:
Здравствуйте, Аноним, Вы писали:

А>В базе есть миллион клиентов, для каждого клиента нужно сгенерировать отчет и сохранить обратно в базу.

А>Сейчас на эту задачу уходит 5 часов если выполнять последовательно.
А>Попробовал через пул-потокв (10 потоков) — заняло 4,5 часов.
А>Согласно бизнес требованиям задача должна выполнятся не более 1 часа.

А>Приложение использует Spring + Hibernate и одну базу данных. Потяно что потоки конкурируют за доступ к данным, но все равно мне непонятно почему 10 потоков не дали значительного прироста производительности.


1. надеюсь в базе данных чтение и запись идет по разным таблицам
2. пул потоков соответсвует пулу конекшкнов к базе
3. для операций чтения транзакции задекларированы как read-only
4. правильно настроено/отключено кеширование в Hibernate
5. если модель данных не слишком сложная, имеет смысл отказаться от Hibernate в пользу JDBC
к пунктам 4-5 следует переходить после тщательного профилирования
Re: Ускорение выполнения задачи
От: Безон Великобритания  
Дата: 17.07.09 12:15
Оценка:
Здравствуйте, Аноним, Вы писали:

А>В базе есть миллион клиентов, для каждого клиента нужно сгенерировать отчет и сохранить обратно в базу.

А>Сейчас на эту задачу уходит 5 часов если выполнять последовательно.
А>Попробовал через пул-потокв (10 потоков) — заняло 4,5 часов.
А>Согласно бизнес требованиям задача должна выполнятся не более 1 часа.


А>Приложение использует Spring + Hibernate и одну базу данных. Потяно что потоки конкурируют за доступ к данным, но все равно мне непонятно почему 10 потоков не дали значительного прироста производительности.


А почему должны были дать?
-----
Re[2]: Ускорение выполнения задачи
От: Blazkowicz Россия  
Дата: 17.07.09 12:38
Оценка:
Здравствуйте, Безон, Вы писали:

Б>А почему должны были дать?

А почему нет? Без узких мест распареллеливание даже на одноядерном процессоре даст видимый прирост.
Re: Ускорение выполнения задачи
От: Ocelot  
Дата: 17.07.09 12:52
Оценка:
Присоединяюсь к предыдущим ораторам.

Плюс еще можно попробовать подгружать данные не одного клиента за запрос, а сразу пачку, и затем раздавать обработчикам.
Дальше, посмотреть, как грузятся отчеты — возможно, стоит также нагенерить пачку и потом каким-нибудь BULK UNSERT.
Кроме того, использовать кеширование при генерации отчета — если есть общий кусок отчета для какой-то группы клиентов,
просто повторно его использовать, а не генерить каждый раз.

Как-то так.
-
Re[3]: Ускорение выполнения задачи
От: Аноним  
Дата: 17.07.09 12:59
Оценка:
Здравствуйте, Blazkowicz, Вы писали:

B>Здравствуйте, Безон, Вы писали:


Б>>А почему должны были дать?

B>А почему нет? Без узких мест распареллеливание даже на одноядерном процессоре даст видимый прирост.

В Java concurrency on practice написанно как расчитывать оптимвальное кол-во потоков.
Никто не даст ссылку на эту статью? Или как вы считаете?
Re[4]: Ускорение выполнения задачи
От: Blazkowicz Россия  
Дата: 17.07.09 13:08
Оценка:
Здравствуйте, Аноним, Вы писали:

А>В Java concurrency on practice написанно как расчитывать оптимвальное кол-во потоков.

А>Никто не даст ссылку на эту статью? Или как вы считаете?
Это?
Re[5]: Ускорение выполнения задачи
От: unkis  
Дата: 17.07.09 13:20
Оценка:
Здравствуйте, Blazkowicz, Вы писали:

B>Здравствуйте, Аноним, Вы писали:


А>>В Java concurrency on practice написанно как расчитывать оптимвальное кол-во потоков.

А>>Никто не даст ссылку на эту статью? Или как вы считаете?
B>Это?
B>

А w- это время ожидания чего?
и я что-то не понимаю, что такое U_cpu?
Re[6]: Ускорение выполнения задачи
От: Blazkowicz Россия  
Дата: 17.07.09 13:24
Оценка:
Здравствуйте, unkis, Вы писали:

U>А w- это время ожидания чего?

Зачастую IO операций. В данном случае если БД находится на другом сервере, то всё время ожизания отклика на запросы.

U>и я что-то не понимаю, что такое U_cpu?

На сколько % мы собираемся загрузить процесоры.
Re[3]: Ускорение выполнения задачи
От: Безон Великобритания  
Дата: 17.07.09 19:03
Оценка:
Здравствуйте, Blazkowicz, Вы писали:

B>Здравствуйте, Безон, Вы писали:


Б>>А почему должны были дать?

B>А почему нет? Без узких мест распареллеливание даже на одноядерном процессоре даст видимый прирост.
А узкие места это что?
ЗЫ. переключение контекста не фига не бесплатно, поэтому бездумное распараллеливание легко просаживает производительность.
-----
Re[4]: Ускорение выполнения задачи
От: Blazkowicz Россия  
Дата: 17.07.09 19:53
Оценка:
Здравствуйте, Безон, Вы писали:

Б>А узкие места это что?

Которые многопоточностью все равно не разрулить, так как они тормозят в любом случае.

Б>ЗЫ. переключение контекста не фига не бесплатно, поэтому бездумное распараллеливание легко просаживает производительность.

На 10 потоках и 2х ядрах можно считать что бесплатное.
Re[5]: Ускорение выполнения задачи
От: Безон Великобритания  
Дата: 18.07.09 05:23
Оценка:
Здравствуйте, Blazkowicz, Вы писали:

B>Здравствуйте, Безон, Вы писали:


Б>>А узкие места это что?

B>Которые многопоточностью все равно не разрулить, так как они тормозят в любом случае.
многопоточностью (в число потоков больше чем число ядер) разгоняется только код большую часть времени находящийся в ожидании данных. Точно так же он разгоняется событийно-управляемым кодом
Б>>ЗЫ. переключение контекста не фига не бесплатно, поэтому бездумное распараллеливание легко просаживает производительность.
B>На 10 потоках и 2х ядрах можно считать что бесплатное.
ну ну блажен кто верует ... Советую написать простой тест без ожидания ввода-вывода
-----
Re[5]: Ускорение выполнения задачи
От: KRA Украина  
Дата: 18.07.09 09:00
Оценка:
Здравствуйте, Blazkowicz, Вы писали:

B>Здравствуйте, Аноним, Вы писали:


А>>В Java concurrency on practice написанно как расчитывать оптимвальное кол-во потоков.

А>>Никто не даст ссылку на эту статью? Или как вы считаете?
B>Это?
B>
На практике эта формула не работает, т.к. время ожидания обычно зависит от количества потоков. Представте, что 100 потоков делают одновременный запрос в БД. В этом случае время ожидания (грубо, время пока БД выполнит чтение данных с диска) будет больше чем, когда один поток делает один запрос.
Re: Ускорение выполнения задачи
От: jalxm Россия  
Дата: 19.07.09 18:50
Оценка:
А>Приложение использует Spring + Hibernate и одну базу данных. Потяно что потоки конкурируют за доступ к данным, но все равно мне непонятно почему 10 потоков не дали значительного прироста производительности.

А>Какие еще могут быть варианты.


может потому что генерация отчета ощутимо кушает cpu?

что если разбить задачу на несколько jobs, где будет обрабатываться определенное кол-во клиентов?
попробуй GridGain (www.gridgain.comwww.gridgain.com):
— пишешь свой task для разбиения на jobs
— запускаешь несколько машин с запущенным GridGain
— jobs автоматически раскидываются по всем grid nodes

Все вопросы скидывай сразу же туда нам в форум
... << RSDN@Home 1.1.4 @@subversion >>
Re[6]: Ускорение выполнения задачи
От: Blazkowicz Россия  
Дата: 20.07.09 09:57
Оценка:
Здравствуйте, Безон, Вы писали:

B>>Которые многопоточностью все равно не разрулить, так как они тормозят в любом случае.

Б>многопоточностью (в число потоков больше чем число ядер) разгоняется только код большую часть времени находящийся в ожидании данных. Точно так же он разгоняется событийно-управляемым кодом
Напоминаю что разговор идет о создании отчетов для миллиона клиентов. Есть подозрения что отчеты создаются не одним запросом.


Б>>>ЗЫ. переключение контекста не фига не бесплатно, поэтому бездумное распараллеливание легко просаживает производительность.

B>>На 10 потоках и 2х ядрах можно считать что бесплатное.
Б>ну ну блажен кто верует ... Советую написать простой тест без ожидания ввода-вывода
Написал. Убедился в своей правоте. Доказывать свою точку зрения на сферическом коне в вакууме можешь сколь угодно долго. Что будет следующим аргументом? Пример где в цикле вызывается Thread.yield()?
Re[7]: Ускорение выполнения задачи
От: denis.zhdanov Россия http://denis-zhdanov.blogspot.com/
Дата: 20.07.09 12:03
Оценка:
Здравствуйте, Blazkowicz, Вы писали:

Б>>>>ЗЫ. переключение контекста не фига не бесплатно, поэтому бездумное распараллеливание легко просаживает производительность.

B>>>На 10 потоках и 2х ядрах можно считать что бесплатное.
Б>>ну ну блажен кто верует ... Советую написать простой тест без ожидания ввода-вывода
B> Написал. Убедился в своей правоте. Доказывать свою точку зрения на сферическом коне в вакууме можешь сколь угодно долго. Что будет следующим аргументом? Пример где в цикле вызывается Thread.yield()?

Написал маленький тест, он показывает, что примерно за одно и то же время при двух потоках успевает выполниться в среднем 78626 итераций, при десяти в среднем 76754 (проверял при четырех подходах на каждый из вариантов (2-10 потоков), amd 64 x2 core dual, os vista 32, jvm 1.6.0_03). При этом время потокам раздается примерно равномерно.

Итого разница составила около двух с половиной процентов, что несколько удвило, т.к. переключение контекста субъективно считалось мною более дорогой операцией. Понятно, что в тесте присутствует достаточно большая погрешность, но тем не менее тенденция налицо.

Если у интересующихся будет желание изменить тест/предложить альтернативный тест, показывающий ощутимо бОльшую разницу из-за переключения конекстов, милости просим, интересно обсудить.


package com;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

public class AAA {

    private static final long DURATION = TimeUnit.NANOSECONDS.convert(30, TimeUnit.SECONDS);
    private static final int THREADS_NUMBER = 50;
    private static final ThreadLocal<AtomicLong> COUNTER = new ThreadLocal<AtomicLong>() {
        @Override
        protected AtomicLong initialValue() {
            return new AtomicLong();
        }
    };
    private static final ThreadLocal<AtomicLong> DUMMY_DATA = new ThreadLocal<AtomicLong>() {
        @Override
        protected AtomicLong initialValue() {
            return new AtomicLong();
        }
    };
    private static final AtomicLong DUMMY_COUNTER = new AtomicLong();
    private static final AtomicLong END_TIME = new AtomicLong(System.nanoTime() + DURATION);

    private static final List<ThreadLocal<CharSequence>> DUMMY_SOURCE = new ArrayList<ThreadLocal<CharSequence>>();
    static {
        for (int i = 0; i < 40; ++i) {
            DUMMY_SOURCE.add(new ThreadLocal<CharSequence>());
        }
    }

    private static final Set<Long> COUNTERS = new ConcurrentSkipListSet<Long>();

    public static void main(String[] args) throws Exception {
        final CountDownLatch startLatch = new CountDownLatch(THREADS_NUMBER);
        final CountDownLatch endLatch = new CountDownLatch(THREADS_NUMBER);

        for (int i = 0; i < THREADS_NUMBER; i++) {
            new Thread() {
                @Override
                public void run() {
                    initDummyData();
                    startLatch.countDown();
                    try {
                        startLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    while (System.nanoTime() < END_TIME.get()) {
                        doJob();
                    }
                    COUNTERS.add(COUNTER.get().get());
                    DUMMY_COUNTER.addAndGet(DUMMY_DATA.get().get());
                    endLatch.countDown();
                }
            }.start();
        }
        startLatch.await();
        END_TIME.set(System.nanoTime() + DURATION);

        endLatch.await();
        printStatistics();
    }

    private static void initDummyData() {
        for (ThreadLocal<CharSequence> threadLocal : DUMMY_SOURCE) {
            threadLocal.set(getRandomString());
        }
    }

    private static CharSequence getRandomString() {
        StringBuilder result = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < 137; ++i) {
            result.append((char)random.nextInt(0xFF));
        }
        return result;
    }

    private static void doJob() {
        Random random = new Random();
        for (ThreadLocal<CharSequence> threadLocal : DUMMY_SOURCE) {
            for (int i = 0; i < threadLocal.get().length(); ++i) {
                DUMMY_DATA.get().addAndGet(threadLocal.get().charAt(i) << random.nextInt(31));
            }
        }
        COUNTER.get().incrementAndGet();
    }

    private static void printStatistics() {
        long total = 0L;
        for (Long counter : COUNTERS) {
            total += counter;
        }
        System.out.printf("Total iterations number: %d, dummy data: %d, distribution:", total, DUMMY_COUNTER.get());
        for (Long counter : COUNTERS) {
            System.out.printf(" %f%%", counter * 100d / total);
        }
    }
}
http://denis-zhdanov.blogspot.com
Re[8]: Ускорение выполнения задачи
От: Аноним  
Дата: 20.07.09 16:16
Оценка:
Здравствуйте, denis.zhdanov, Вы писали:

DZ>Итого разница составила около двух с половиной процентов, что несколько удвило, т.к. переключение контекста субъективно считалось мною более дорогой операцией. Понятно, что в тесте присутствует достаточно большая погрешность, но тем не менее тенденция налицо.


DZ>Если у интересующихся будет желание изменить тест/предложить альтернативный тест, показывающий ощутимо бОльшую разницу из-за переключения конекстов, милости просим, интересно обсудить.

[/java]
У меня 10 потоков выдает бОльший результат, чем 4. 4-ядерный проц, jre 1.6.0_14
Re[9]: Ускорение выполнения задачи
От: Blazkowicz Россия  
Дата: 20.07.09 16:21
Оценка:
Здравствуйте, Аноним, Вы писали:

А>У меня 10 потоков выдает бОльший результат, чем 4. 4-ядерный проц,

Потому что на 4х ядрах 10 потоков работают быстрее чем на 2х.
Re[8]: Ускорение выполнения задачи
От: Безон Великобритания  
Дата: 20.07.09 20:11
Оценка:
Здравствуйте, denis.zhdanov, Вы писали:

DZ>код теста


Тест замечательный. И действительно показывает, что при полной изоляции данных для алгоритмов, параллельное их выполнение не ведет к сильному просаживанию производительности. Правда немного смущает использование Atomic при гарантированно однопоточном доступе.
Однако когда алгоритмы работают над разделяемыми данными, а тем более их модифицируют, вот тогда производительность упадет значительно больше. Насколько больше сказать не могу пока не будет теста.
-----
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.