Можно ли проверить в Java, является ли процессор гиперпоточным?

Я хотел бы знать оптимальное количество потоков, которые я могу запустить. Обычно это равняется Runtime.getRuntime().availableProcessors(),

Однако возвращаемое число в два раза выше на процессоре, поддерживающем гиперпоточность. Теперь для некоторых задач гиперпоточность хороша, но для других она ничего не делает. Я подозреваю, что в моем случае это ничего не делает, и поэтому я хочу знать, нужно ли мне делить число, возвращенное Runtime.getRuntime().availableProcessors() в двоем.

Для этого я должен определить, является ли процессор гиперпоточным. Отсюда мой вопрос - как я могу сделать это на Java?

Благодарю.

РЕДАКТИРОВАТЬ

ОК, я проверил мой код Вот мое окружение:

  • Lenovo ThinkPad W510 (т.е. процессор i7 с 4 ядрами и гиперпоточностью), 16 ГБ ОЗУ
  • Windows 7
  • 84 архивных CSV-файла размером от 105 до 16 миллионов
  • Все файлы читаются один за другим в главном потоке - нет многопоточного доступа к HD.
  • Каждая строка файла CSV содержит некоторые данные, которые анализируются, и быстрый контекстный тест определяет, является ли строка релевантной.
  • Каждая соответствующая строка содержит два двойных (представляющих долготу и широту для любопытных), которые приводятся в единый Long, который затем сохраняется в общем наборе хэшей.

Таким образом, рабочие потоки ничего не читают с HD, но занимаются разархивированием и разбором содержимого (используя библиотеку opencsv).

Ниже приведен код без скучных деталей:

public void work(File dir) throws IOException, InterruptedException {
  Set<Long> allCoordinates = Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
  int n = 6;
  // NO WAITING QUEUE !
  ThreadPoolExecutor exec = new ThreadPoolExecutor(n, n, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>());
  StopWatch sw1 = new StopWatch();
  StopWatch sw2 = new StopWatch();
  sw1.start();
  sw2.start();
  sw2.suspend();
  for (WorkItem wi : m_workItems) {
    for (File file : dir.listFiles(wi.fileNameFilter)) {
      MyTask task;
      try {
        sw2.resume();
        // The only reading from the HD occurs here:
        task = new MyTask(file, m_coordinateCollector, allCoordinates, wi.headerClass, wi.rowClass);
        sw2.suspend();
      } catch (IOException exc) {
        System.err.println(String.format("Failed to read %s - %s", file.getName(), exc.getMessage()));
        continue;
      }
      boolean retry = true;
      while (retry) {
        int count = exec.getActiveCount();
        try {
          // Fails if the maximum of the worker threads was created and all are busy.
          // This prevents us from loading all the files in memory and getting the OOM exception.
          exec.submit(task);
          retry = false;
        } catch (RejectedExecutionException exc) {
          // Wait for any worker thread to finish
          while (exec.getActiveCount() == count) {
            Thread.sleep(100);
          }
        }
      }
    }
  }
  exec.shutdown();
  exec.awaitTermination(1, TimeUnit.HOURS);
  sw1.stop();
  sw2.stop();
  System.out.println(String.format("Max concurrent threads = %d", n));
  System.out.println(String.format("Total file count = %d", m_stats.getFileCount()));
  System.out.println(String.format("Total lines = %d", m_stats.getTotalLineCount()));
  System.out.println(String.format("Total good lines = %d", m_stats.getGoodLineCount()));
  System.out.println(String.format("Total coordinates = %d", allCoordinates.size()));
  System.out.println(String.format("Overall elapsed time = %d sec, excluding I/O = %d sec", sw1.getTime() / 1000, (sw1.getTime() - sw2.getTime()) / 1000));
}

public class MyTask<H extends CsvFileHeader, R extends CsvFileRow<H>> implements Runnable {
  private final byte[] m_buffer;
  private final String m_name;
  private final CoordinateCollector m_coordinateCollector;
  private final Set<Long> m_allCoordinates;
  private final Class<H> m_headerClass;
  private final Class<R> m_rowClass;

  public MyTask(File file, CoordinateCollector coordinateCollector, Set<Long> allCoordinates,
                Class<H> headerClass, Class<R> rowClass) throws IOException {
    m_coordinateCollector = coordinateCollector;
    m_allCoordinates = allCoordinates;
    m_headerClass = headerClass;
    m_rowClass = rowClass;
    m_name = file.getName();
    m_buffer = Files.toByteArray(file);
  }

  @Override
  public void run() {
    try {
      m_coordinateCollector.collect(m_name, m_buffer, m_allCoordinates, m_headerClass, m_rowClass);
    } catch (IOException e) {
      e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
    }
  }
}

Пожалуйста, найдите ниже результаты (я немного изменил вывод, чтобы пропустить повторяющиеся части):

Max concurrent threads = 4
Total file count = 84
Total lines = 56395333
Total good lines = 35119231
Total coordinates = 987045
Overall elapsed time = 274 sec, excluding I/O = 266 sec

Max concurrent threads = 6
Overall elapsed time = 218 sec, excluding I/O = 209 sec

Max concurrent threads = 7
Overall elapsed time = 209 sec, excluding I/O = 199 sec

Max concurrent threads = 8
Overall elapsed time = 201 sec, excluding I/O = 192 sec

Max concurrent threads = 9
Overall elapsed time = 198 sec, excluding I/O = 186 sec

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

7 ответов

Решение

За Windows, если количество логических ядер больше, чем количество ядер, у вас есть hyper-threading включен. Подробнее об этом читайте здесь.

Ты можешь использовать wmic найти эту информацию:

C:\WINDOWS\system32>wmic CPU Get NumberOfCores,NumberOfLogicalProcessors /Format:List


NumberOfCores=4
NumberOfLogicalProcessors=8

Следовательно, моя система имеет hyper-threading, Количество логических процессоров вдвое больше ядер.

Но вам, возможно, даже не нужно знать. Runtime.getRuntime().availableProcessors() уже возвращает количество логических процессоров.

Полный пример получения количества физических ядер (Windows только):

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class PhysicalCores
{
    public static void main(String[] arguments) throws IOException, InterruptedException
    {
        int physicalNumberOfCores = getPhysicalNumberOfCores();
        System.out.println(physicalNumberOfCores);
    }

    private static int getPhysicalNumberOfCores() throws IOException, InterruptedException
    {
        ProcessBuilder processBuilder = new ProcessBuilder("wmic", "CPU", "Get", "NumberOfCores");
        processBuilder.redirectErrorStream(true);
        Process process = processBuilder.start();
        String processOutput = getProcessOutput(process);
        String[] lines = processOutput.split(System.lineSeparator());
        return Integer.parseInt(lines[2]);
    }

    private static String getProcessOutput(Process process) throws IOException, InterruptedException
    {
        StringBuilder processOutput = new StringBuilder();

        try (BufferedReader processOutputReader = new BufferedReader(
                new InputStreamReader(process.getInputStream())))
        {
            String readLine;

            while ((readLine = processOutputReader.readLine()) != null)
            {
                processOutput.append(readLine);
                processOutput.append(System.lineSeparator());
            }

            process.waitFor();
        }

        return processOutput.toString().trim();
    }
}

К сожалению, это невозможно из Java. Если вы знаете, что приложение будет работать в современном варианте Linux, вы можете прочитать файл /proc/cpuinfo и определить, включен ли HT.

Чтение результатов этой команды делает свое дело:

grep -i "physical id" /proc/cpuinfo | sort -u | wc -l

Еще немного размышлений:

  • Hyperthreading может иметь более 2 потоков на код (Sparc может иметь 8)
  • Для работы сборщика мусора требуется процессорное время.
  • Гиперпоточность может помочь одновременному GC - или не может; или JVM может потребовать быть эксклюзивным (не гиперпоточным) владельцем ядра. Таким образом, затруднение ГХ для получения лучших результатов во время теста может повредить в долгосрочной перспективе.
  • Гиперпоточность обычно полезна, если есть ошибки в кэше, поэтому ЦП не останавливается, а переключается на другую задачу. Следовательно, "к гиперпоточности или нет" будет зависеть как от рабочей нагрузки, так и от размера кэш-памяти CPU L1/L2 / скорости памяти и т. Д.
  • Операционные системы могут иметь смещение в сторону / против некоторых потоков, и Thread.setPriority может не учитываться (в Linux это обычно не учитывается).
  • Можно установить сходство процесса, запретив некоторые ядра. Поэтому зная, что есть гиперпоточность, в таких случаях не будет никакого существенного достоинства.

Тем не менее, вы должны иметь настройку для размера рабочих потоков и рекомендации, как настроить, учитывая специфику архитектуры.

Нет надежного способа определить, включена ли у вас гиперпоточность, отключена ли гиперпоточность или нет.

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

Другой подход заключается в использовании всех процессоров, даже если гиперпоточность не помогает (при условии, что это не делает код значительно медленнее).

Невозможно определить это по чистой Java (в конце концов, логическое ядро ​​- это ядро, если оно реализовано с использованием HT или нет). Помните, что предложенные решения могут удовлетворить ваши требования (как вы и просили), но не только процессоры Intel предлагают форму гиперпоточности (Sparc приходит на ум, и я уверен, что есть и другие).

Вы также не приняли во внимание, что даже если вы определите, что система использует HT, вы не сможете контролировать сродство потоков с ядрами из Java. Таким образом, вы все еще во власти планировщика потоков ОС. Несмотря на то, что существуют вероятные сценарии, в которых меньшее количество потоков может работать лучше (из-за уменьшения объема кэш-памяти), невозможно статически определить, сколько потоков следует использовать (после того, как все процессоры имеют очень разные размеры кэша (диапазон от 256 КБ на нижнем уровне) до>16 МБ в серверах можно ожидать в наши дни. И это неизбежно будет меняться с каждым новым поколением).

Просто сделайте его настраиваемым параметром, любая попытка определить это без точного знания целевой системы бесполезна.

Возможно, вам не удастся надежно запросить ОС или время выполнения, но вы можете выполнить быстрый тест.

Постепенно увеличивайте потоки спин-блокировки, проверяя, повторяется ли каждый новый поток так же, как предыдущий. Как только производительность одного из потоков становится меньше, чем примерно половина каждого из предыдущих тестов (по крайней мере, для Intel, я не знаю о SPARC), вы знаете, что вы начали делить ядро ​​с гиперпотоками.

Нет никакого способа сделать это. Одна вещь, которую вы можете сделать, это создать пул потоков Runtime.getRuntime().availableProcessors() Потоки в вашем приложении и использовать, как в случае поступления запроса.

Таким образом, вы можете иметь 0 - Runtime.getRuntime().availableProcessors() количество потоков.

Другие вопросы по тегам