по основной теме. Если это не так, вы можете сделать это так, выполняя работу во временном расположении для каждого работника, а затем обновляя общее местоположение только один раз, когда работа завершена. Любое повторное обновление одного и того же местоположения работниками будет просто ложным делением без реальной цели.

отреть возможностьN потоки, выполняющие асинхронные задачи с малым значением результата, напримерdouble или жеint64_t, Так о8 Значения результата могут занимать одну строку кэша ЦП.N равно количеству ядер процессора.

С одной стороны, если я просто выделю массивN предметы, каждыйdouble или жеint64_t, тогда8 потоки будут совместно использовать строку кэша ЦП, которая кажется неэффективной.

С другой стороны, если я выделю целую строку кэша для каждогоdouble/int64_t, поток получателя должен будет получитьN строки кэша, каждая из которых написана отдельным ядром процессора (кроме 1).

Так есть ли эффективное решение для этого сценария? Процессор x86-64. Решение в C ++ является предпочтительным.

Пояснение 1: издержки запуска / выхода потока невелики, поскольку используется пул потоков. Так что это в основном синхронизация в критической секции.

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

 Serge Rogatch13 сент. 2017 г., 11:32
@IwillnotexistIdonotexist, это часто возможно, если не всегда.
 Serge Rogatch12 сент. 2017 г., 12:44
@Walter, да, потоки сообщают результат только один раз после его вычисления. Однако я хотел бы разбрасывать и собирать миллионы таких параллельных вычислений в секунду. Другими словами, главный поток распределяет работу, запускает рабочие потоки, затем собирает результаты, что-то делает с ними, а затем снова запускает параллельные вычисления.
 Walter12 сент. 2017 г., 12:39
Я не совсем понимаю. Работа, выполняемая каждым потоком, должна, очевидно, избегать любого ложного совместного использования (чтение / запись в строки кэша, обычные для других потоков), но сообщать результат обратно в систему.один раз вполне может быть сделано для общей строки кэша. Таким образом, критический вопрос здесь, как часто вашприемник получать данные за каждый раз, когда они записаны / назначены?
 Galik12 сент. 2017 г., 12:38
Кажется, у вас есть только 2 варианта, написать массив или написать независимые переменные. Я хотел бы попробовать оба и посмотреть, кто победит.
 BeeOnRope13 сент. 2017 г., 23:21
Например, для пробуждения потоков и последующего информирования потребителя о том, что все рабочие выполнены, как правило, также требуется некоторая синхронизация. Возможно, передача значения результата одного значения на одного работника может быть просто поддержана этим механизмом!

Ответы на вопрос(2)

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

Если поток чтения прочитает результаты из N потоков, то все эти ошибки в кеше будут происходить параллельно.

Из вашего комментария:

Я хотел бы разбрасывать и собирать миллионы таких параллельных вычислений в секунду. Другими словами, главный поток распределяет работу, запускает рабочие потоки, затем собирает результаты, что-то делает с ними, а затем снова запускает параллельные вычисления.

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

Дайте каждому работнику выводмассивгде он хранит последовательные результаты различных задач, которые он выполнил. Фактические массивы могут быть длиной всего 4 тыс. Записей или что-то в этом роде, с некоторой синхронизацией, чтобы позволить автору записи обернуться и повторно использовать первую половину, как только читатель запустил вторую половину буфера этого потока.

Когда поток коллектора считывает результат из одного из этих массивов, он переносит эту строку кэша в свои собственные кэши L2 / L1D, принося вместе с собой 7 других результатов в той же строке кэша (при условии, что обычный случай, когда рабочий поток уже имеет заполнено все 8int64_t слотов и не будет писать эту строку кеша снова для этой группы крошечных задач).

Или, лучше, собирать их в пакетах, выровненных по строкам кэша, чтобы при пропущенных конфликтах не удалялась строка кэша из L1D сборщика, пока он не вернулся к нему. (Уменьшите вероятность этого, переместив массивы результатов с различным смещением для каждого потока, чтобы поток коллектора не считывал N строк кэша, которые все смещены друг от друга на кратное 4 кБ или около того.)

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

В противном случае вытакже нужны общие переменные текущей выходной позиции, которые рабочие обновляют (с помощью release-store) после записи выходного массива. (Может быть, пакетное обновление этих счетчиков позиций приводит к одному на 8 результатов массива. Но убедитесь, что вы делаете это с чисто атомарным хранилищем, а не с+= 8, Поскольку поток производителя является единственным, который записывает эту переменную, было бы глупо иметь накладные расходыlock add.)

Это может легко вызвать ложное совместное использование между рабочими потоками, если они упакованы в один массив, и также обязательно должно быть кэшировано (не в памяти UC или WC, чтобы рабочий поток мог эффективно перезаписать его на месте). Таким образом, вы определенно хотите, чтобы каждый поток имел свою собственную строку кэша для них. Сборщик просто должен будет понести наказание за чтение N различных строк кэша (и, вероятно, из-за неправильной спекуляции памяти:Каковы затраты времени ожидания и пропускной способности совместного использования производителем и потребителем места в памяти между гипер-братьями и сестрами по сравнению с не-гипер-братьями и сестрами?)

На самом деле, лучший вариант в этом случае, вероятно, будетиспользовать одно из 8 слов в каждой строке кэша выходных массивов в качестве флага завершения или растровое изображение, поэтому поток сборщика может проверить это, чтобы увидеть, готовы ли результаты 7 в строке кэша.

Если основным узким местом является просто получение результатов между потоками рабочих и сборщиков, то, вероятно, ваши потоки слишком тонкие. Вы должны более грубо разбить свои задачи или попросить рабочие потоки объединить несколько полученных результатов, в то время как они все еще горячи в своем L1D. Этомного лучшая пропускная способность, чем передача на другое ядро ​​через L3 или DRAM.

 BeeOnRope13 сент. 2017 г., 23:28
Знание того, как все это работает, очень помогло бы. Если на самом деле не существует существующей системы, и это все просто теоретическое, то я действительно думаю, что это просто не имеет значения: вам нужно разобраться с другими вещами, прежде чем это будет иметь значение, и детали очень важны. Только тогда, может быть, вы можете попытаться оптимизировать этот аспект (но, как указано выше, возможно, вы можете бесплатно передать результат в свой механизм синхронизации). Даже помимо этого, есть множество других важных деталей: все рабочие партии закончатся почти в одно и то же время? Может ли потребитель выполнить некоторую работу до получения всех результатов?
 Serge Rogatch13 сент. 2017 г., 08:47
Я думаю, что нет, это сильно меняет и усложняет дизайн, и главный поток может нуждаться в некоторой синхронизации (для защиты от параллельных запросов от других пользователей), поэтому я боюсь перенести все это на рабочие потоки.
 Serge Rogatch13 сент. 2017 г., 08:30
К сожалению, хотя есть некоторые применимые моменты, этот ответ в значительной степени основан на неправильном понимании, как будто партии являются независимыми. Действительно, главный поток может запустить следующий пакет параллельных вычислений только после того, как он соберет и обработает результаты предыдущего пакета. Потому что результаты предыдущей партии служат некоторыми параметрами следующей партии.
 BeeOnRope13 сент. 2017 г., 23:24
Проблема в том, что вы на самом деле не дали достаточно подробностей, чтобы Питер дал полный ответ, не так ли? Нормальная модель «распределить -> потребить» включает в себя блокировку главного потока, которая обычно составляет тысячи циклов или более, поэтому вы выполняете тысячи циклов работы, чтобы она имела смысл, и десятки циклов накладных расходов на синхронизацию равны одному ». «Возвращение результата» не имеет значения. Поэтому я предполагаю, что у вас есть более быстрый неблокирующий способ для ожидания главного потока, который вращается в некоторой области памяти на некоторых таких.
 Peter Cordes13 сент. 2017 г., 08:32
@SergeRogatch: Вместо того, чтобы возвращаться к главному потоку, можно ли каждому рабочему потоку прочитать результаты других потоков и продублировать вычисления, необходимые для выяснения, что делать дальше? Результат дублирует работу, но сохраняет синхронизацию в оба конца.

вую отчетность / чтение из заголовка / основного потока, то

Вы должны избегать ложного обмена (используя общую строку кэша) между работниками. Это должно быть сделано с помощью автоматических переменных (которые на самом деле могут быть реализованы только для регистра) для внутренней работы.Передача результатов обратно (или входам) в главный поток менее критична по эффективности и может использовать массив (то есть общую строку кэша). Здесь вы можете просто поэкспериментировать, что работает лучше всего.
 BeeOnRope13 сент. 2017 г., 23:31
У меня сложилось впечатление, что каждый работник будет писать только в значение результатаодин раз за партию работы, которая затем будет прочитанаодин раз по основной теме. Если это не так, вы можете сделать это так, выполняя работу во временном расположении для каждого работника, а затем обновляя общее местоположение только один раз, когда работа завершена. Любое повторное обновление одного и того же местоположения работниками будет просто ложным делением без реальной цели.
 Peter Cordes13 сент. 2017 г., 00:13
@SergeRogatch: Это звучит как плохая идея. Руководство Intel предлагает не использовать хранилища NT для одной линии с мьютексом; IDK, если это опасно и может вызвать непоследовательное поведение или что-то, но это все равно звучит как плохая идея. WC и UC ужасны, потому что записи должны идти до DRAM, а WB или WT оба типа кешируемой памяти, поэтому вы получаете ложное совместное использование.
 Serge Rogatch12 сент. 2017 г., 15:37
Я думаю, что должна быть хитрость, такая как выбор другой стратегии кэширования (WB / WC / WT и т. Д.), Чтобы рабочие потоки могли просто записывать свои 8 байтов каждый без извлечения всей строки кэша для их использования. Или какой-то не временный намек может помочь, если есть что-то, что позволяет ему достигать только кеша L3, без копирования на кеш ядра L1 / L2. Поэтому для эксперимента мне нужно еще несколько вариантов, кроме двух, которые я описал в этом вопросе (и я думаю, они могут дать такую ​​же производительность, которая слишком мала для моих нужд).

Ваш ответ на вопрос