2013-11-28 1 views
3

В нашем проекте для одной задачи мы использовали статический случайный экземпляр для цели генерации случайных чисел. После выпуска Java 7 появился новый класс ThreadLocalRandom для генерации случайных чисел.ThreadLocalRandom с общим статическим экспериментом сравнения производительности случайных экземпляров

Из спецификации:

При необходимости, использование ThreadLocalRandom, а не совместно Случайные объекты в параллельных программах, как правило, сталкиваются с гораздо меньше накладных расходов и раздор. Использование ThreadLocalRandom особенно подходит, когда несколько задач (например, каждый из ForkJoinTask) используют случайные числа параллельно в пулах потоков.

, а также:

Когда все обычаи этой формы, то никогда нельзя случайно разделить ThreadLocalRandom между несколькими потоками.

Так что я сделал свой небольшой тест:

public class ThreadLocalRandomTest { 

private static final int THREAD_COUNT = 100; 
private static final int GENERATED_NUMBER_COUNT = 1000; 
private static final int INT_RIGHT_BORDER = 5000; 
private static final int EXPERIMENTS_COUNT = 5000; 

public static void main(String[] args) throws InterruptedException { 
    System.out.println("Number of threads: " + THREAD_COUNT); 
    System.out.println("Length of generated numbers chain for each thread: " + GENERATED_NUMBER_COUNT); 
    System.out.println("Right border integer: " + INT_RIGHT_BORDER); 
    System.out.println("Count of experiments: " + EXPERIMENTS_COUNT); 

    int repeats = 0; 
    int workingTime = 0; 
    long startTime = 0; 
    long endTime = 0; 

    for (int i = 0; i < EXPERIMENTS_COUNT; i++) { 
     startTime = System.currentTimeMillis(); 
     repeats += calculateRepeatsForSharedRandom(); 
     endTime = System.currentTimeMillis(); 
     workingTime += endTime - startTime; 
    } 
    System.out.println("Average repeats for shared Random instance: " + repeats/EXPERIMENTS_COUNT 
      + ". Average working time: " + workingTime/EXPERIMENTS_COUNT + " ms."); 

    repeats = 0; 
    workingTime = 0; 
    for (int i = 0; i < EXPERIMENTS_COUNT; i++) { 
     startTime = System.currentTimeMillis(); 
     repeats += calculateRepeatsForTheadLocalRandom(); 
     endTime = System.currentTimeMillis(); 
     workingTime += endTime - startTime; 
    } 
    System.out.println("Average repeats for ThreadLocalRandom: " + repeats/EXPERIMENTS_COUNT 
      + ". Average working time: " + workingTime/EXPERIMENTS_COUNT + " ms."); 
} 

private static int calculateRepeatsForSharedRandom() throws InterruptedException { 
    final Random rand = new Random(); 
    final Map<Integer, Integer> counts = new HashMap<>(); 

    for (int i = 0; i < THREAD_COUNT; i++) { 
     Thread thread = new Thread() { 
      @Override 
      public void run() { 

       for (int j = 0; j < GENERATED_NUMBER_COUNT; j++) { 
        int random = rand.nextInt(INT_RIGHT_BORDER); 
        if (!counts.containsKey(random)) { 
         counts.put(random, 0); 
        } 
        counts.put(random, counts.get(random) + 1); 
       } 
      } 
     }; 
     thread.start(); 
     thread.join(); 
    } 

    int repeats = 0; 
    for (Integer value : counts.values()) { 
     if (value > 1) { 
      repeats += value; 
     } 
    } 

    return repeats; 
} 

private static int calculateRepeatsForTheadLocalRandom() throws InterruptedException { 
    final Map<Integer, Integer> counts = new HashMap<>(); 

    for (int i = 0; i < THREAD_COUNT; i++) { 
     Thread thread = new Thread() { 
      @Override 
      public void run() { 

       for (int j = 0; j < GENERATED_NUMBER_COUNT; j++) { 
        int random = ThreadLocalRandom.current().nextInt(INT_RIGHT_BORDER); 
        if (!counts.containsKey(random)) { 
         counts.put(random, 0); 
        } 
        counts.put(random, counts.get(random) + 1); 
       } 
      } 
     }; 
     thread.start(); 
     thread.join(); 
    } 

    int repeats = 0; 
    for (Integer value : counts.values()) { 
     if (value > 1) { 
      repeats += value; 
     } 
    } 

    return repeats; 
} 

}

Я также добавил, тест на неразделяемую Random и получили следующие результаты:

Number of threads: 100 
Length of generated numbers chain for each thread: 100 
Right border integer: 5000 
Count of experiments: 10000 
Average repeats for non-shared Random instance: 8646. Average working time: 13 ms. 
Average repeats for shared Random instance: 8646. Average working time: 13 ms. 
Average repeats for ThreadLocalRandom: 8646. Average working time: 13 ms. 

Что касается меня, это немного странно, я ожидал хотя бы увеличения скорости при использовании ThreadLocalRandom по сравнению с общим экземпляром Random, но не вижу разницы вообще.

Может кто-нибудь объяснить, почему это работает так, может быть, я ничего не понял. Заранее спасибо.

+0

Позвольте мне попугать специалистов: микрообъектив трудно получить. Если вы хотите попробовать, используйте [Caliper] (https://code.google.com/p/caliper/), чтобы сделать это. –

+1

ну, в первую очередь, ваши потоки не параллельны, когда вы «присоединяетесь» к нити сразу после запуска одного перед запуском другого. –

+1

** намного меньше накладных расходов и утверждение ** не означает ** это быстрее ** это просто означает * при определенных типах и уровнях напряжения может быть улучшена пропускная способность и стабильность *. – OldCurmudgeon

ответ

3

Вы не используете что-либо параллельно, потому что вы ожидаете завершения каждого потока сразу после его запуска. Вам нужен цикл ожидания за пределами цикла, который запускает потоки:

List<Thread> threads = new ArrayList<Thread>(); 
for (int i = 0; i < THREAD_COUNT; i++) { 
    Thread thread = new Thread() { 
     @Override 
     public void run() { 

      for (int j = 0; j < GENERATED_NUMBER_COUNT; j++) { 
       int random = rand.nextInt(INT_RIGHT_BORDER); 
       if (!counts.containsKey(random)) { 
        counts.put(random, 0); 
       } 
       counts.put(random, counts.get(random) + 1); 
      } 
     } 
    }; 
    threads.add(thread); 
    thread.start(); 
} 

for (Thread thread: threads) { 
    thread.join(); 
} 
3

Ваш тестовый код является ошибочным для одного. Проклятие бенчмаркеров повсюду.

thread.start(); 
thread.join(); 

почему бы не сохранить Locs и написать

thread.run(); 

результат тот же.

EDIT: Если вы не понимаете результат из вышеизложенного, это означает, что вы выполняете однопоточные тесты, не происходит многопоточности.

1

Возможно, было бы проще просто взглянуть на то, что на самом деле происходит. Вот источник для ThreadLocal.get(), который также называется для ThreadLocalRandom.current().

public T get() { 
    Thread t = Thread.currentThread(); 
    ThreadLocalMap map = getMap(t); 
    if (map != null) { 
     ThreadLocalMap.Entry e = map.getEntry(this); 
     if (e != null) 
      return (T)e.value; 
    } 
    return setInitialValue(); 
} 

Где ThreadLocalMap - это специализированная реализация, подобная HashMap, с оптимизацией.

Так что в основном происходит то, что ThreadLocal содержит карту Thread-> Object - или в этом случае Thread-> Random - которая затем просматривается и возвращается или создается.Поскольку это ничего не «волшебное», время будет равно HashMap-lookup + первоначальной накладной на создание фактического объекта, который будет возвращен. Поскольку поиск в HashMap (в этом оптимизированном случае) является линейным, стоимость поиска составляет k, где k - это стоимость калькуляции хэш-функции.

Таким образом, вы можете сделать некоторые предположения:

  • ThreadLocal будет быстрее, чем создание объекта каждый раз, когда в каждом Runnable, если затраты на создание не намного меньше, чем K. Таким образом, поиск Random - это хорошая вещь, если положить int внутри, возможно, не настолько умный.

  • ThreadLocal будет лучше, чем использование вашей собственной HashMap, так как такая общая реализация может быть принята равной k или еще хуже.

  • ThreadLocal будет медленнее, чем при использовании любого вида со стоимостью < k. Пример: сначала сохраните все в массиве, затем сделайте myRandoms[threadID].
    Но тогда это предполагает, что вы знаете, какие потоки будут обрабатывать вашу работу в первую очередь, так что это не настоящий кандидат на ThreadLocal в любом случае.

Смежные вопросы