Здравствуйте, denis.zhdanov, Вы писали:
DZ>Итого разница составила около двух с половиной процентов, что несколько удвило, т.к. переключение контекста субъективно считалось мною более дорогой операцией. Понятно, что в тесте присутствует достаточно большая погрешность, но тем не менее тенденция налицо.
Само по себе переключение контекста занимает весьма немного времени.
Порядка 2.000-4.000 тактов процессора.
Обоснования:
1)
Вот выдержки из класса java.util.concurrent.Exchanger:
public class Exchanger<V> {
...
private static final int NCPU = Runtime.getRuntime().availableProcessors();
....
/**
* The number of times to spin (doing nothing except polling a
* memory location) before blocking or giving up while waiting to
* be fulfilled. Should be zero on uniprocessors. On
* multiprocessors, this value should be large enough so that two
* threads exchanging items as fast as possible block only when
* one of them is stalled (due to GC or preemption), but not much
* longer, to avoid wasting CPU resources. Seen differently, this
* value is a little over half the number of cycles of an average
* context switch time on most systems. The value here is
* approximately the average of those across a range of tested
* systems.
*/private static final int SPINS = (NCPU == 1) ? 0 : 2000;
т.е. авторы java.util.concurrent считают, что на большинстве многоядерных систем переключение контекста лежит в диапазоне 3000-4000 тактов процессора.
2)
Вот выдержки из класса java.util.concurrent.SynchronousQueue:
/**
* The number of nanoseconds for which it is faster to spin
* rather than to use timed park. A rough estimate suffices.
*/static final long spinForTimeoutThreshold = 1000L;
Если считать типичной тактовой частотой 2-3ГГц, то выходит, что авторы java.util.concurrent считают, что время, соизмеримое с переключением контекста порядка 2000-3000 тактов
3)
Следственный эксперимент: класс PingPong запускает два класса Player, а они перебрасываются классом Item.
Item содержит простой счетчик типа int, считающий количество передач. Передачи осуществляются через механизм wait()/notify().
На тестовом компьютере (AMD Duron 1300, WinXP, jdk 1.0.6_13) 100.000.000 передач было выполнено за 232с, т.е. на одно переключение контекста уходило в среднем 232 * 10 * 1.3 = 3016 тактов.
Код:
public class Item {
int counter = 0;
}
public class PingPong {
// constantpublic static final int ITERATION_COUNT = 100 * 1000 * 1000;
// global variables :(public static final Item item = new Item();
public static final CountDownLatch start = new CountDownLatch(1);
public static final CountDownLatch stop = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
// init sectionnew Thread(new Player(false)).start(); //player #0new Thread(new Player(true)).start(); //player #1
// work sectionlong startTime = System.nanoTime();
start.countDown();
stop.await();
long deltaTime = System.nanoTime() - startTime;
// info-to-console section
System.out.println("deltaTime: " + deltaTime);
}
}
public class Player implements Runnable {
private final boolean imOdd;
public Player(boolean imOdd) {
this.imOdd = imOdd;
}
public void run() {
// await for start ping-pongingtry {
PingPong.start.await();
} catch (InterruptedException e) {e.printStackTrace();}
synchronized (PingPong.item) {
while (true) {
// wait for my turn (avoid spirious wakeup)while (((PingPong.item.counter & 1) == 1) == imOdd) {
try {
PingPong.item.wait(); // wait for "Ping"
} catch (InterruptedException e) {e.printStackTrace();}
}
// increment Ping-Poin counter
PingPong.item.counter++;
// do "Pong"
PingPong.item.notify();
if (PingPong.item.counter >= PingPong.ITERATION_COUNT) {
PingPong.stop.countDown(); // show for PingPoint.class that my work doingreturn; // break, thats all
}
}
}
}
}
Здравствуйте, Безон, Вы писали:
B>> А почему нет? Без узких мест распареллеливание даже на одноядерном процессоре даст видимый прирост. Б>Пока только видно что прироста это не дает, а есть даже потери.
Для тех кто в танке: "В базе есть миллион клиентов, для каждого клиента нужно сгенерировать отчет и сохранить обратно в базу"
Ускорение выполнения задачи
От:
Аноним
Дата:
17.07.09 07:00
Оценка:
В базе есть миллион клиентов, для каждого клиента нужно сгенерировать отчет и сохранить обратно в базу.
Сейчас на эту задачу уходит 5 часов если выполнять последовательно.
Попробовал через пул-потокв (10 потоков) — заняло 4,5 часов.
Согласно бизнес требованиям задача должна выполнятся не более 1 часа.
Приложение использует Spring + Hibernate и одну базу данных. Потяно что потоки конкурируют за доступ к данным, но все равно мне непонятно почему 10 потоков не дали значительного прироста производительности.
Здравствуйте, Аноним, Вы писали:
А>В базе есть миллион клиентов, для каждого клиента нужно сгенерировать отчет и сохранить обратно в базу. А>Сейчас на эту задачу уходит 5 часов если выполнять последовательно. А>Попробовал через пул-потокв (10 потоков) — заняло 4,5 часов. А>Согласно бизнес требованиям задача должна выполнятся не более 1 часа.
Таки поставить профайлер и посмотреть на какие этапы уходить больше процессорного времени.
А>Приложение использует Spring + Hibernate и одну базу данных. Потяно что потоки конкурируют за доступ к данным,
Потоки конкурируют на чтение??? Надеюсь вы отчеты без Hibernate строите?
А>но все равно мне непонятно почему 10 потоков не дали значительного прироста производительности.
Возможно потому что узкое место не в CPU?
Здравствуйте, Аноним, Вы писали:
А>В базе есть миллион клиентов, для каждого клиента нужно сгенерировать отчет и сохранить обратно в базу. А>Сейчас на эту задачу уходит 5 часов если выполнять последовательно. А>Попробовал через пул-потокв (10 потоков) — заняло 4,5 часов. А>Согласно бизнес требованиям задача должна выполнятся не более 1 часа.
А>Приложение использует Spring + Hibernate и одну базу данных. Потяно что потоки конкурируют за доступ к данным, но все равно мне непонятно почему 10 потоков не дали значительного прироста производительности.
1. надеюсь в базе данных чтение и запись идет по разным таблицам
2. пул потоков соответсвует пулу конекшкнов к базе
3. для операций чтения транзакции задекларированы как read-only
4. правильно настроено/отключено кеширование в Hibernate
5. если модель данных не слишком сложная, имеет смысл отказаться от Hibernate в пользу JDBC
к пунктам 4-5 следует переходить после тщательного профилирования
Здравствуйте, Аноним, Вы писали:
А>В базе есть миллион клиентов, для каждого клиента нужно сгенерировать отчет и сохранить обратно в базу. А>Сейчас на эту задачу уходит 5 часов если выполнять последовательно. А>Попробовал через пул-потокв (10 потоков) — заняло 4,5 часов. А>Согласно бизнес требованиям задача должна выполнятся не более 1 часа.
А>Приложение использует Spring + Hibernate и одну базу данных. Потяно что потоки конкурируют за доступ к данным, но все равно мне непонятно почему 10 потоков не дали значительного прироста производительности.
Здравствуйте, Безон, Вы писали:
Б>А почему должны были дать?
А почему нет? Без узких мест распареллеливание даже на одноядерном процессоре даст видимый прирост.
Плюс еще можно попробовать подгружать данные не одного клиента за запрос, а сразу пачку, и затем раздавать обработчикам.
Дальше, посмотреть, как грузятся отчеты — возможно, стоит также нагенерить пачку и потом каким-нибудь BULK UNSERT.
Кроме того, использовать кеширование при генерации отчета — если есть общий кусок отчета для какой-то группы клиентов,
просто повторно его использовать, а не генерить каждый раз.
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, Безон, Вы писали:
Б>>А почему должны были дать? B>А почему нет? Без узких мест распареллеливание даже на одноядерном процессоре даст видимый прирост.
В Java concurrency on practice написанно как расчитывать оптимвальное кол-во потоков.
Никто не даст ссылку на эту статью? Или как вы считаете?
Здравствуйте, Аноним, Вы писали:
А>В Java concurrency on practice написанно как расчитывать оптимвальное кол-во потоков. А>Никто не даст ссылку на эту статью? Или как вы считаете?
Это?
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, Аноним, Вы писали:
А>>В Java concurrency on practice написанно как расчитывать оптимвальное кол-во потоков. А>>Никто не даст ссылку на эту статью? Или как вы считаете? B>Это? B>
А w- это время ожидания чего?
и я что-то не понимаю, что такое U_cpu?
Здравствуйте, unkis, Вы писали:
U>А w- это время ожидания чего?
Зачастую IO операций. В данном случае если БД находится на другом сервере, то всё время ожизания отклика на запросы.
U>и я что-то не понимаю, что такое U_cpu?
На сколько % мы собираемся загрузить процесоры.
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, Безон, Вы писали:
Б>>А почему должны были дать? B>А почему нет? Без узких мест распареллеливание даже на одноядерном процессоре даст видимый прирост.
А узкие места это что?
ЗЫ. переключение контекста не фига не бесплатно, поэтому бездумное распараллеливание легко просаживает производительность.
Здравствуйте, Безон, Вы писали:
Б>А узкие места это что?
Которые многопоточностью все равно не разрулить, так как они тормозят в любом случае.
Б>ЗЫ. переключение контекста не фига не бесплатно, поэтому бездумное распараллеливание легко просаживает производительность.
На 10 потоках и 2х ядрах можно считать что бесплатное.
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, Безон, Вы писали:
Б>>А узкие места это что? B>Которые многопоточностью все равно не разрулить, так как они тормозят в любом случае.
многопоточностью (в число потоков больше чем число ядер) разгоняется только код большую часть времени находящийся в ожидании данных. Точно так же он разгоняется событийно-управляемым кодом Б>>ЗЫ. переключение контекста не фига не бесплатно, поэтому бездумное распараллеливание легко просаживает производительность. B>На 10 потоках и 2х ядрах можно считать что бесплатное.
ну ну блажен кто верует ... Советую написать простой тест без ожидания ввода-вывода
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, Аноним, Вы писали:
А>>В Java concurrency on practice написанно как расчитывать оптимвальное кол-во потоков. А>>Никто не даст ссылку на эту статью? Или как вы считаете? B>Это? B>
На практике эта формула не работает, т.к. время ожидания обычно зависит от количества потоков. Представте, что 100 потоков делают одновременный запрос в БД. В этом случае время ожидания (грубо, время пока БД выполнит чтение данных с диска) будет больше чем, когда один поток делает один запрос.
А>Приложение использует Spring + Hibernate и одну базу данных. Потяно что потоки конкурируют за доступ к данным, но все равно мне непонятно почему 10 потоков не дали значительного прироста производительности.
А>Какие еще могут быть варианты.
может потому что генерация отчета ощутимо кушает cpu?
что если разбить задачу на несколько jobs, где будет обрабатываться определенное кол-во клиентов?
попробуй GridGain (www.gridgain.comwww.gridgain.com):
— пишешь свой task для разбиения на jobs
— запускаешь несколько машин с запущенным GridGain
— jobs автоматически раскидываются по всем grid nodes
Здравствуйте, Безон, Вы писали:
B>>Которые многопоточностью все равно не разрулить, так как они тормозят в любом случае. Б>многопоточностью (в число потоков больше чем число ядер) разгоняется только код большую часть времени находящийся в ожидании данных. Точно так же он разгоняется событийно-управляемым кодом
Напоминаю что разговор идет о создании отчетов для миллиона клиентов. Есть подозрения что отчеты создаются не одним запросом.
Б>>>ЗЫ. переключение контекста не фига не бесплатно, поэтому бездумное распараллеливание легко просаживает производительность. B>>На 10 потоках и 2х ядрах можно считать что бесплатное. Б>ну ну блажен кто верует ... Советую написать простой тест без ожидания ввода-вывода
Написал. Убедился в своей правоте. Доказывать свою точку зрения на сферическом коне в вакууме можешь сколь угодно долго. Что будет следующим аргументом? Пример где в цикле вызывается Thread.yield()?
Здравствуйте, 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);
}
}
}
Здравствуйте, denis.zhdanov, Вы писали:
DZ>Итого разница составила около двух с половиной процентов, что несколько удвило, т.к. переключение контекста субъективно считалось мною более дорогой операцией. Понятно, что в тесте присутствует достаточно большая погрешность, но тем не менее тенденция налицо.
DZ>Если у интересующихся будет желание изменить тест/предложить альтернативный тест, показывающий ощутимо бОльшую разницу из-за переключения конекстов, милости просим, интересно обсудить.
[/java]
У меня 10 потоков выдает бОльший результат, чем 4. 4-ядерный проц, jre 1.6.0_14
Здравствуйте, Аноним, Вы писали:
А>У меня 10 потоков выдает бОльший результат, чем 4. 4-ядерный проц,
Потому что на 4х ядрах 10 потоков работают быстрее чем на 2х.
Здравствуйте, denis.zhdanov, Вы писали:
DZ>код теста
Тест замечательный. И действительно показывает, что при полной изоляции данных для алгоритмов, параллельное их выполнение не ведет к сильному просаживанию производительности. Правда немного смущает использование Atomic при гарантированно однопоточном доступе.
Однако когда алгоритмы работают над разделяемыми данными, а тем более их модифицируют, вот тогда производительность упадет значительно больше. Насколько больше сказать не могу пока не будет теста.
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, Безон, Вы писали:
B>>>Которые многопоточностью все равно не разрулить, так как они тормозят в любом случае. Б>>многопоточностью (в число потоков больше чем число ядер) разгоняется только код большую часть времени находящийся в ожидании данных. Точно так же он разгоняется событийно-управляемым кодом B>Напоминаю что разговор идет о создании отчетов для миллиона клиентов. Есть подозрения что отчеты создаются не одним запросом.
Ну обычно массированных вычислений на клиенте бд не производят.
Б>>>>ЗЫ. переключение контекста не фига не бесплатно, поэтому бездумное распараллеливание легко просаживает производительность. B>>>На 10 потоках и 2х ядрах можно считать что бесплатное. Б>>ну ну блажен кто верует ... Советую написать простой тест без ожидания ввода-вывода B> Написал. Убедился в своей правоте. Доказывать свою точку зрения на сферическом коне в вакууме можешь сколь угодно долго. Что будет следующим аргументом? Пример где в цикле вызывается Thread.yield()?
ну не совсем например очередь через wait notify
Здравствуйте, Blazkowicz, Вы писали:
B> Написал. Убедился в своей правоте. Доказывать свою точку зрения на сферическом коне в вакууме можешь сколь угодно долго. Что будет следующим аргументом? Пример где в цикле вызывается Thread.yield()?
Ты сначала докажи вот это утверждение:
B> А почему нет? Без узких мест распареллеливание даже на одноядерном процессоре даст видимый прирост.
Пока только видно что прироста это не дает, а есть даже потери.
Здравствуйте, Безон, Вы писали:
Б>Тест замечательный. И действительно показывает, что при полной изоляции данных для алгоритмов, параллельное их выполнение не ведет к сильному просаживанию производительности. Правда немного смущает использование Atomic при гарантированно однопоточном доступе. Б>Однако когда алгоритмы работают над разделяемыми данными, а тем более их модифицируют, вот тогда производительность упадет значительно больше. Насколько больше сказать не могу пока не будет теста.
Тест и был задуман работать с thread-scoped данными, чтобы издержки от многопоточности выражались только в переключении контекста. Atomic используется только потому, что быстрее написать код, который вызывает incrementAndGet(), чем инкрементить вручную.
Понятно, что при lock contention производительность проседать будет, и сильно, но изначально вызвало спор утверждение, что десять потоков на двух ядрах дадут заметную деградацию производительности из-за переключения контекста.
Здравствуйте, Аноним, Вы писали:
А>В базе есть миллион клиентов, для каждого клиента нужно сгенерировать отчет и сохранить обратно в базу. А>Сейчас на эту задачу уходит 5 часов если выполнять последовательно. А>Попробовал через пул-потокв (10 потоков) — заняло 4,5 часов. А>Согласно бизнес требованиям задача должна выполнятся не более 1 часа.
Раз в базе миллион клиентов то объем вероятно достаточно большой?
А>Приложение использует Spring + Hibernate и одну базу данных. Потяно что потоки конкурируют за доступ к данным, но все равно мне непонятно почему 10 потоков не дали значительного прироста производительности.
А>Какие еще могут быть варианты.
Есть вариант что производительность упирается в СУБД (кстати какая она). Я бы рекомендовал использовать шардинг данных (например, разбить таблицы на партиции по компаниям), разделив всю базу на такое количество шард, чтобы каждая целиком помещалась в память (вместе с индексами). После этого в несколько потоков генерировать отчеты по компаниям из одной шарды.
Здравствуйте, Безон, Вы писали:
Б>Тест замечательный. И действительно показывает, что при полной изоляции данных для алгоритмов, параллельное их выполнение не ведет к сильному просаживанию производительности. Правда немного смущает использование Atomic при гарантированно однопоточном доступе. Б>Однако когда алгоритмы работают над разделяемыми данными, а тем более их модифицируют, вот тогда производительность упадет значительно больше. Насколько больше сказать не могу пока не будет теста.
При полной изоляции данных — каждый поток будет обновлять кэш данных процессора (а возможно и кэш кода), а это процедура более затратная чем переключение контекста. Как раз, полагаю, желательно для массивных по данным вычислений (обработка аудио, видео, фото, работа с большими текстами) желательно иметь максимальное кол-во общих данных на чтение. Для записи использовать какие-то неблокирующие разделяемые структуры (так понимаю обращение к атомарным переменным занимает порядка 100-200циклов). Т.е., например, для декодирования JPG в массив цветов, думаю разумнее не делить одному потоку первые 1000 квадратов, второму — вторую тысячу, а как раз динамически выделять каждому по запросу, скажем, по 10 квадратов подряд.
Само по себе переключение контекста в "чистом виде" занимает порядка 2.000-4.000 тактов процессора.
Заметно больше может уходить на сопутствующие процессы. Например, на повторную загрузку кэшей процессора данными и кодом, которые были вытеснены другими потоками.
Следственный эксперимент: класс PingPong запускает два класса Player, а они перебрасываются классом Item.
Item содержит простой счетчик типа int, считающий количество передач. Передачи осуществляются через механизм wait()/notify().
Кроме того, каждый поток пробегает по одному из массивов (считывает каждый 16-ый елемент для подгрузки массива в кэш) (четный поток читает cache_1, нечетный — cache_0).
На тестовом компьютере (AMD Duron 1300(L1_cache size=64K+64K(code+data), L2_cache size=64K), WinXP, jdk 1.0.6_13) размер кэша первого уровня для данных 64К.
Эксперимент показывает, что оптимальная производительность достигается в том случае, когда каждый поток использует массивы интов по 8192(т.е. 2 массива по 32Кб) (deltaTime/cacheSize: 779640) и производительность в 7.24 раза хуже при использовании массивово интов по 16384(т.е. 2 массива по 64Кб) (deltaTime/cacheSize: 5643590).
Во втором случае каждый из потоков полностью загружает кэш данных первого уровня своими данными выталкивая данные другого.
// в коде все размеры в интах, т.е. размеры в байтах в 4 раза больше
Дополнительный эксперимент. Просто так, под рукой был код и я решил продемонстрировать как выглядят размеры кэшей с точки зрения явиста :
Пусть оба потока работают с одним массивом и покажем что в тот момент когда массив перестает влезать в кэш происходит катастрофическое падение производительности.
Модифицируем строки
PingPong.java:
public static final int ARRAY_LENGTH = 16 * 1024;
заменим на
public static final int ARRAY_LENGTH = 32 * 1024;
Видно, что при размере массива 16384(64К) производительность(641119) близка к оптимальной(626351), а при при размере массива 32768(128К) производительность(5617316) ухудшилась в 8.76 раз.
При чем ухудшение началось ровно в тот момент, как массив перестал помещаться в кэш (переход от 16384 елементов к 17408 елементов и ухудшение производительности 1.16 раза).
Исходный код:
public class Item {
int counter = 0;
}
public class PingPong {
// constantpublic static final int ITERATION_COUNT = 1000000;
public static final int ARRAY_LENGTH = 16 * 1024;
public static final int CACHE_INDEX_STEP = 16;
// global variables :(public static int[] cache_0 = new int[ARRAY_LENGTH]; // 64K (size(int) = 4)public static int[] cache_1 = new int[ARRAY_LENGTH]; // 64K (size(int) = 4)public static Item item;
public static CountDownLatch start;
public static CountDownLatch stop;
public static void main(String[] args) throws InterruptedException {
for (int cacheSize = 2 * 1024; cacheSize <= 16 * 1024; cacheSize += 1 * 1024) {
// init section
item = new Item();
start = new CountDownLatch(1);
stop = new CountDownLatch(2);
new Thread(new Player(cacheSize, false)).start();
new Thread(new Player(cacheSize, true)).start();
// work sectionlong startTime = System.nanoTime();
start.countDown();
stop.await();
long deltaTime = System.nanoTime() - startTime;
// info-to-console section
System.out.println("cacheSize: " + cacheSize + " deltaTime: " + deltaTime + " deltaTime/cacheSize: " + deltaTime/cacheSize);
}
}
}
public class Player implements Runnable {
private final int cacheSize;
private final boolean imOdd;
public Player(int cacheSize, boolean imOdd) {
this.cacheSize = cacheSize;
this.imOdd = imOdd;
}
public void run() {
// await for start ping-pongingtry {
PingPong.start.await();
} catch (InterruptedException e) {e.printStackTrace();}
synchronized (PingPong.item) {
while (true) {
// wait for my turn (avoid spirious wakeup)while (((PingPong.item.counter & 1) == 1) == imOdd) {
try {
PingPong.item.wait(); // wait for "Ping"
} catch (InterruptedException e) {e.printStackTrace();}
}
// increment Ping-Poin counter
PingPong.item.counter++;
// read array elements for loading cachesint sum = 0;
int[] cache = imOdd ? PingPong.cache_0 : PingPong.cache_1; // different players choice different arraysfor (int indx = 0; indx < cacheSize; indx += PingPong.CACHE_INDEX_STEP) {
sum += cache[indx];
}
// do "Pong"
PingPong.item.notify();
if (PingPong.item.counter >= PingPong.ITERATION_COUNT) {
PingPong.stop.countDown(); // show for PingPoint.class that my work doingreturn; // break, thats all
}
}
}
}
}
Здравствуйте, Аноним, Вы писали:
А>Какие еще могут быть варианты.
На текущем проекте столкнулись с системой построения отчетов, которой мы предоставляем данные, а она по ним генерирует всякие разные отчеты. В результате дискуссий с ними выяснилось, что они раз в сутки забирают у нас данные за день из плоских таблиц, они над ними производят разные трансформации в другие более простые таблицы, часто специфические для каждого отчета. А только потом по этим данным быстро генерят конечные отчеты.
Так конечно дикие кластера, но возможно стоит так же копнуть в сторону промежуточных трансформаций и упращение данных.
Re: Ускорение выполнения задачи
От:
Аноним
Дата:
15.09.09 07:24
Оценка:
Здравствуйте, Аноним, Вы писали:
А>В базе есть миллион клиентов, для каждого клиента нужно сгенерировать отчет и сохранить обратно в базу. А>Сейчас на эту задачу уходит 5 часов если выполнять последовательно. А>Попробовал через пул-потокв (10 потоков) — заняло 4,5 часов. А>Согласно бизнес требованиям задача должна выполнятся не более 1 часа.
Попробуйте реализовать эту логику в виде хранимой процедуры(stored procedure). Сделайте хотя бы прототип. + Посмотрите планы запросов. Разберитесь с индексами.
Если у вас есть выделенный DBA, обратитесь к нему.
Понимаю, что business logic на Java- это прогрессивнее, чем корявые языки PL/SQL (Oracle) или T-SQL (MS SQLServer).
Но иногда без них никак не обойтись. Кстати сейчас и MySQL поддерживает хранимые процедуры.
Re: Ускорение выполнения задачи
От:
Аноним
Дата:
15.09.09 07:34
Оценка:
Здравствуйте, Аноним, Вы писали:
А>В базе есть миллион клиентов, для каждого клиента нужно сгенерировать отчет и сохранить обратно в базу. А>Сейчас на эту задачу уходит 5 часов если выполнять последовательно. А>Попробовал через пул-потокв (10 потоков) — заняло 4,5 часов. А>Согласно бизнес требованиям задача должна выполнятся не более 1 часа.
А>Какие еще могут быть варианты.
Попробуйте загрузить данные из базы в память вашего Java процесса и запроцессить эти данные без обращения к БД.
Миллион записей — не ахти сколько. Главное — дайте Java побольше памяти.
Проверьте скорость обмена между компом, где живет сервер БД, и компом, где запускается Java программа. Если канал узкий, то переместите либо сервер БД и Java программу поближе друг к другу.
В часе 3.6 млн миллисекунд, т.е. вам надо тратить на один отчет в среднем 3.6 миллисекунды. Чтобы уложиться в это время нужно ооочень сильно постараться.
1. Выпилите из проекта Hibernate, оптимизируйте вручную JDBC-запросы, словари закешируйте.
2. Измените порядок операций. Если возможно, читайте сразу большие объемы данных в память в структуры, более подходящие для предполагаемого алгоритма доступа к ним. Добавляйте отчеты в базу пачками.
3. (Альтернатива п.2) Предвычисления. Не всегда возможны, но подумать об этом стоит, т.к. могут очень сильно экономить время.
4. Опытным путём подберите оптимальное число потоков выполнения (не фиксируйте это число — на другом железе может быть оптимальной другая цифра).