Здесь есть что-то специфическое для гипертрединга?
Я думал про ситуации когда потокам на одном ядре не хватает L1 или L2 кеша.
Пожалуй нет, разве что при дроблении на большее количество потоков уменьшится грануляция записи. Тем более что запись двумя потоками в одну строку кэша на одном физическом ядре не приводит к штрафу (кэши не привязаны к потокам, они работают в физическом пространстве адресов). Но обычно распределение потоков по ядрам отдано ОС, а уж как она там их распихает - никому не ведомо (там же вмешается и балансировка нагрузки, и прыганье частот каждого ядра, и остальные потоки в ОС тоже не все остановлены).
Канал между кэшами довольно быстр (32-64 байта за такт), объём L3 превышает суммарный объём всех L2, так что просто нехватка L2 (про L1 вообще молчу) слабо скажется на скорости. Ну, чаще всего, исключения всегда возможны. А вот пропасть между скоростью и латентностью памяти и кэшем L3 - в разы больше, потому и её влияние сильнее.
на 2-х ядрах прирост в 2 раза, а потом почти никакого толку.
Очень странно. И если две цифры - это разброс двух запусков, то интервал слишком велик, так не должно быть, разброс не должен превышать нескольких процентов, смотрите может что в фоне запущено.
Отсутствие прироста после двух потоков намекает что почти половина работы (не по объёму массива, а по занимаемому времени) распределилась в один поток (неважно с каким номером) и вся программа ждёт его завершения. Если дело именно в этом, то стоит делить работу на более мелкие куски (я предпочитаю порядка 0.1с) и каждым потоком обрабатывать первый же готовый кусок циклом до окончания готовых кусков, тогда все потоки завершатся примерно с такой же погрешностью.
это нужно ассемлер использовать?
Если компилятор не умеет сам векторизировать циклы, то скорее всего почти да: есть технология
Intrinsics, но разбираться с командами SSE/AVX таки придётся.
-- 02.01.2020, 21:02 --Поделить на мелкие блоки можно примерно так (вызов и ожидание завершения те же):
int curBlock = 0;
#define incBlock 1000;
#define numBlocks 1000000;
static unsigned int __stdcall MyFun() {
int base, stop;
while (true) do {
CS.Lock;//Критическая секция
base = curBlock; curBlock += incBlock; stop = numBlock;
CS.Leave;//Критическая секция
if (base >= stop) return 0;//Больше работы нет
if (base + incBlock < stop) stop = base + incBlock;//Последний блок может быть меньше, а остальные одинакового размера
for (int i = base; i < stop; i++) {
... //Основная работа
}
}
}