Но как я понял, это будет работать только на тех версиях Windows, где работает и GetCurrentProcessorNumber(). Т.е. не будет работать под WinXP/2k.
2. С помощью инструкции CPUID получать номер APIC ID, по статической таблице переводить его в номер процессора. Это будет работать на всех версиях Windows. Практически то, что нужно, мне даже переводить в номер процессора не нужно, и так устроит, главное иметь уникальное число для каждого процессора.
Но тут полностью не устраивает, что на моём компьютере CPUID с EAX=1 выполняется 284 такта (полностью сериализует конвеер и выполняет полный барьер памяти).
В идеале хотелось чего-то вроде получения идентификатора текущего потока из блока информации потока.
з.ы. Не уверен, куда это больше — к WinAPI или Низкоуровнему программированию, поэтому пока закросспостю в оба форума. Если что можно из одного удалить.
Здравствуйте, IID, Вы писали:
IID>Здравствуйте, remark, Вы писали:
IID>а если сразу после получение номера процессора поток будет перепланирован на другой процессор ?
Могу предположить, что тогда поток будет выполняться на другом процессоре.
Здравствуйте, remark, Вы писали:
R>Здравствуйте, IID, Вы писали:
IID>>Здравствуйте, remark, Вы писали:
IID>>а если сразу после получение номера процессора поток будет перепланирован на другой процессор ?
R>Могу предположить, что тогда поток будет выполняться на другом процессоре.
R>
Смысл банальный — что в момент получения номера номер был правильный.
Это именно то, что требуется узнать, а именно — "номер в момент получения", а не "номер миллисекундой позже", — чувствуете разницу?
Здравствуйте, IID, Вы писали:
IID>Здравствуйте, remark, Вы писали:
R>>Здравствуйте, IID, Вы писали:
IID>>>Здравствуйте, remark, Вы писали:
IID>>>а если сразу после получение номера процессора поток будет перепланирован на другой процессор ?
R>>Могу предположить, что тогда поток будет выполняться на другом процессоре.
IID>а какой тогда смысл в полученном номере ?
А какой смысл был бы в противном случае?
Если без шуток и это действительно интересно, то это значение можно охарактерировать как "хинт текущего процессора правильный в большинстве случаев и некоторый промежуток времени после выполнения функции". И именно с этой стороны его можно и нужно использовать. В реальности этот хинт обычно используется для контроля работы примитивов синхронизации, либо для оптимизации примитивов синхронизации.
За то, что он всё же нужен на практике говорит тот факт, что эту функция добавлена в WinAPI и не так давно в Linux.
U>Это именно то, что требуется узнать, а именно — "номер в момент получения", а не "номер миллисекундой позже", — чувствуете разницу?
а момент получения — это когда? до вызова call, после call? точно посередке вызывааемой функции? перед ret? после ret?
R>Если без шуток и это действительно интересно, то это значение можно охарактерировать как "хинт текущего процессора правильный в большинстве случаев и некоторый промежуток времени после выполнения функции".
Если только как отправную точку для сбора некоторой статистики, для которой не нужна абсолютная точность (см. выделенное), то да... Но опираться на полученное число, например, для организации логики какого-то своего примитива синхронизации, ИМХО неверно. Одно переключение между получением и использованием — и приплыли.
U>>Это именно то, что требуется узнать, а именно — "номер в момент получения", а не "номер миллисекундой позже", — чувствуете разницу? А>а момент получения — это когда? до вызова call, после call? точно посередке вызывааемой функции? перед ret? после ret?
Здравствуйте, IID, Вы писали:
IID>Если только как отправную точку для сбора некоторой статистики, для которой не нужна абсолютная точность (см. выделенное), то да... Но опираться на полученное число, например, для организации логики какого-то своего примитива синхронизации, ИМХО неверно. Одно переключение между получением и использованием — и приплыли.
Для обеспечения корректности опираться на аккуратность номера текущего процессора неправильно, а для, например, оптимизации, вполне можно.
U>А это как вам больше нравится .
А мне никак не нравится. Такой вызов имеет смысл только на IRQL>=DISPATCH_LEVEL остальное — быдлокодерство, основанное на женской логике.
Здравствуйте, Аноним, Вы писали:
U>>А это как вам больше нравится . А>А мне никак не нравится. Такой вызов имеет смысл только на IRQL>=DISPATCH_LEVEL остальное — быдлокодерство, основанное на женской логике.
Сразу видно — человек не занимался эффективной синхронизацией
R>Сразу видно — человек не занимался эффективной синхронизацией
IDD прав, и прав на 100%. Не занимайтесь фигней, только если это не ваше хобби. Синхронизации в пользовательском режиме не строят на знании номера текущего процессора. Для общности можете всегда его считать равным одному. Сделайте через дефайн....
Здравствуйте, Andrew.W Worobow, Вы писали:
AWW>Здравствуйте, remark, Вы писали:
R>>Сразу видно — человек не занимался эффективной синхронизацией
AWW>IDD прав, и прав на 100%.
Здравствуйте, remark, Вы писали:
R>Здравствуйте, Andrew.W Worobow, Вы писали:
AWW>>Здравствуйте, remark, Вы писали:
R>>>Сразу видно — человек не занимался эффективной синхронизацией
AWW>>IDD прав, и прав на 100%.
R>Могу только сказать, что IDD и те, кто его поддерживают, не разбираются в эффективной синхронизации. R>Почему все как огня бояться этой вещи
Постараюсь в ближайшее время показать реальный пример использования этой функции.
Здравствуйте, remark, Вы писали:
R>>>Сразу видно — человек не занимался эффективной синхронизацией AWW>>IDD прав, и прав на 100%. R>Могу только сказать, что IDD и те, кто его поддерживают, не разбираются в эффективной синхронизации.
Да что ты говоришь. Пока что я вижу, что к сожалению у тебя имеются некоторые пробелы в понимании многозадачности. И видимо отсюда и стремление получить то что ненужно.
Попробуй просто описать идею, тогда проще будет тебе обьяснить твою ошибки.
R>Почему все как огня бояться этой вещи
Да никто не боится, просто возможно модель у тебя в голове не соответстует реалиям. Вполне возможно и отсюда стемление получить то что и так есть например.
AWW>> Синхронизации в пользовательском режиме не строят на знании номера текущего процессора.
R>Это аксиома или мантра?
Это просто очевидно. На 100%. Либо афинити маска, либо ничего.
Здравствуйте, remark, Вы писали:
R>Здравствуйте, Аноним, Вы писали:
U>>>А это как вам больше нравится . А>>А мне никак не нравится. Такой вызов имеет смысл только на IRQL>=DISPATCH_LEVEL остальное — быдлокодерство, основанное на женской логике.
R>Сразу видно — человек не занимался эффективной синхронизацией
Здравствуйте, Andrew.W Worobow, Вы писали:
R>>>>Сразу видно — человек не занимался эффективной синхронизацией
AWW>Да что ты говоришь. Пока что я вижу, что к сожалению у тебя имеются некоторые пробелы в понимании многозадачности. И видимо отсюда и стремление получить то что ненужно.
AWW>Попробуй просто описать идею, тогда проще будет тебе обьяснить твою ошибки.
Отвечаю сразу всем. А именно: Andrew.W Worobow, IID, Геннадий Майко.
Вначале вспомогательный вопрос — почему в WinAPI была добавлена "ненужная", "неправильная", "быдлокодерская" и так далее функция GetCurrentProcessorNumber()? И так же в Linux API аналогичная функция vgetcpu()? Сам факт добавления такой функции в API двух основных ОС наталкивает на мысли.
Относительно WinAPI мы скорее всего не увидим официальных причин добавления, а вот с Linux всё значительно прозрачнее — вот выдержка из сопроводительной записки к патчу с функцией vgetcpu():
I got several requests over the years to provide a fast way to get
the current CPU and node on x86-64. That is useful for a couple of things:
— The kernel gets a lot of benefit from using per CPU data to get better
cache locality and avoid cache line bouncing. This is currently
not quite possible for user programs. With a fast way to know the current
CPU user space can use per CPU data that is likely in cache already.
Locking is still needed of course — after all the thread might switch
to a different CPU — but at least the memory should be already in cache
and locking on cached memory is much cheaper.
— For NUMA optimization in user space you really need to know the current
node to find out where to allocate memory from.
If you allocate a fresh page from the kernel the kernel will give you
one in the current node, but if you keep your own pools like most programs
do you need to know this to select the right pool.
On single threaded programs it is usually not a big issue because they
tend to start on one node, allocate all their memory there and then eventually
use it there too, but on multithreaded programs where threads can
run on different nodes it's a bigger problem to make sure the threads
can get node local memory for best performance.
At first look such a call still looks like a bad idea — after all the kernel can
switch a process at any time to other CPUs so any result of this call might
be wrong as soon as it returns.
But at a closer look it really makes sense:
— The kernel has strong thread affinity and usually keeps a process on the
same CPU. So switching CPUs is rare. This makes it an useful optimization.
The alternative is usually to bind the process to a specific CPU — then it
"know" where it is — but the problem is that this is nasty to use and
requires user configuration. The kernel often can make better decisions on
where to schedule. And doing it automatically makes it just work.
This cannot be done effectively in user space because only the kernel
knows how to get this information from the CPUs because it requires
translating local APIC numbers to Linux CPU numbers.
Doing it in a syscall is too slow so doing it in a vsyscall makes sense.
...
My eventual hope is that glibc will be start using this to implement a NUMA aware
malloc() in user space that tries to allocate local memory preferably.
I would say that's the biggest gap we still have in "general purpose" NUMA tuning
on Linux. Of course it will be likely useful for a lot of other scalable
code too.
...
-Andi Kleen
Конкретный пример. Задача — поддерживать некоторый счётчик в многопоточной среде. Вот два варианта решения. Первый — в лоб — с помощью InterlockedIncrement() над одной переменной. Второй — распределённый — на основе GetCurrentProcessorNumber().
namespace arch
{
unsigned const cacheline_size = 128;
unsigned const processor_count = 4;
}
struct padded_value
{
long volatile value;
char pad [arch::cacheline_size - sizeof(long)];
};
padded_value values [arch::processor_count];
void thread_func1()
{
for (int i = 0; i != 10000000; ++i)
{
if (i % 1000)
{
_InterlockedIncrement(&values[0].value);
}
else
{
volatile int x = values[0].value;
}
}
}
void thread_func2()
{
for (int i = 0; i != 10000000; ++i)
{
if (i % 1000)
{
_InterlockedIncrement(&values[GetCurrentProcessorNumber()].value);
}
else
{
volatile int x = 0;
for (int j = 0; j != arch::processor_count; ++j)
{
x += values[j].value;
}
}
}
}
Я провёл бенчмарк обоих функций. Запускал 32 потока на машине c 4-ёх-ядерным процессором в 4-ёх вариациях:
1. все потоки привязаны к одному ядру, это фактически эмуляция одноядерного процессора
2. все потоки привязаны к ядрам 0 и 1, это фактически эмуляция двухядерного процессора
3. все потоки привязаны к ядрам 0 и 2, это фактически эмуляция двухпроцессорной машины, т.к. кэш L2 разделяется между ядрами 0 и 1, и 2 и 3 соотв.
4. без привязки
Вот результат для второго варианта с GetCurrentProcessorNumber():
кол-во тактов на итерацию
масштабирование
1
26
1
2
28
1.854
3
28
1.785
4
28
3.645
Ну что ж, масштабирование 3.645 на 4-ёх ядрах — это приемлимо. Теперь смотрим первый вариант, без использования GetCurrentProcessorNumber():
кол-во тактов на итерацию
масштабирование
1
24
1
2
76
0.6361
3
120
0.4038
4
376
0.2585
Я думаю, тут комментарии излишни.
Вот так с помощью стрёмной функции, иногда выдающей неправильные результаты можно изменить масштабирование примитива с "линейно убывающего" на "линейно возрастающее".
Теперь об альтернативах GetCurrentProcessorNumber(). Собственно видится 2 основные альтернативы. Первая — использовать жёсткую привязку потоков к процессорам, тогда всегда можно сказать, на каком процессоре выполняется код. Иногда это — решение. Но зачастую жёсткая привязка это не то, с чем хочется связываться. Andi Kleen охарактеризовал это так:
but the problem is that this is nasty to use and
requires user configuration. The kernel often can make better decisions on
where to schedule. And doing it automatically makes it just work
Ну и достаточно просто упомянуть библиотечный код, что бы стало понятно, что это *не* общее решение.
Вторая альтернатива — использовать везде идентификатор потока вместо идентификатора процессора. Зачастую это — *хорошее* решение. В конце концов идентификатор потока не меняется и можно рассчитывать на 100% точность. Но вот 3 причины, которые сразу приходят к голову, почему это *не* универсальное решение и почему идентификатор процессора может быть лучше в каких-то ситуациях:
— Потоков может быть значительно больше, чем процессоров. Процессоров обычно 1, 2, 4. Потоков — 10, 20, 40. Соотв. когда дело дойдёт до пуллинга или агрегации придётся платить за это количество.
— Кол-во процессоров не изменяется во время работы. Соотв. можно, например, при инициализации примитива сразу выделить блоков памяти по кол-ву процессоров. С потоками значительно проблематичнее — они могут создаваться и удаляться во время работы. Допустим нам надо иметь блок памяти на каждый поток. Мы оказываемся в достаточно затруднительном положении.
— От данных, связанных с потоком, основной смысл тогда, когда работа с ними ведётся через обычные операции, а не атомарные (Interlocked). Но если мы, например, строим примитив взаимного исключения, то с данными, связанными с потоком, всё равно придётся работать с помощью атомарных операций. А тогда и весь смысл теряется по сравнению с данными, связанными с процессором. Только хуже, т.к. потоков больше, чем процессоров.
В принципе возможен третий вариант — использование хэша чего-либо (того же идентификатора потока, или произвольного указателя). Но если хэш используется там, где вместо него можно было бы использовать хинт номера процессора, то хэш тут всегда худшее решение.
Исходя из этого, использование номера текущего процессора может быть оправдано. Пример со счётчиком, конечно, немного надуман, но я сейчас строю на такой же основе масштабируемый распределенный reader-writer mutex. Идея, в принципе, достаточно тривиальна — я думаю, что аналогичные решения должны применяться в ядрах ОС. Такой мьютекс будет обладать линейной масштабируемостью на стороне читателя и допускать истинно параллельный доступ на чтение, в отличие от традиционных reader-writer mutex, которые во многих ситуациях не позволяют истинно параллельного доступа на чтение, обходятся дороже чем простой мьютекс и обладают отрицательной масштабируемостью ещё худшей, чем у обычного мьютекса.
Я надеюсь, что часть вопросов отпала. Осталось только понять как можно получить GetCurrentProcessorNumber() под WinXP
У вашего примера есть один фатальный недостаток — счетчик меряет сам себя.
В итоге в случае с
_InterlockedIncrement(&values[0].value);
имеем забавную картину: всё процессоры непрерывно ломятся в один и тот же адрес пямяти.
По определению, они могут это делать только по очереди, иначе бы терялся весь смысл InterlockedIncrement'а.
Отсюда и масштабируемость 1/кол-во процессоров.
К сожалению, в реальной жизни счётчики применяются для измерения чего-нибудь полезного.
Добавьте в ваш код хотябы вызов QueryPerformanceCounter и получите масштабируемость по количеству процессоров.
Резюмируя вынужден заключить, что если вы не разрабатываете сверхбыстрый менеджер памяти (вот для них и добавлен vgetcpu) или что-то навроде, то вы всё-таки занимаетесь быдлокодерством. Для поддержки некоторого счётчика в многопоточной среде таких наворотов не требуется.
Здравствуйте, Блудов Павел, Вы писали:
БП>Здравствуйте, remark!
БП>У вашего примера есть один фатальный недостаток — счетчик меряет сам себя. БП>В итоге в случае с БП>
БП>_InterlockedIncrement(&values[0].value);
БП>
БП>имеем забавную картину: всё процессоры непрерывно ломятся в один и тот же адрес пямяти. БП>По определению, они могут это делать только по очереди, иначе бы терялся весь смысл InterlockedIncrement'а. БП>Отсюда и масштабируемость 1/кол-во процессоров.
Отсюда бы следовала масштабируемость 1, а не 1/N.
БП>К сожалению, в реальной жизни счётчики применяются для измерения чего-нибудь полезного. БП>Добавьте в ваш код хотябы вызов QueryPerformanceCounter и получите масштабируемость по количеству процессоров.
Хорошо, это разумно. Я добавлю некую локальную полезную работу тактов на 500 между инкрементами и приведу результаты.
В любом случае, добавление дополнительной работы не как не изменит *абсолютной* стоимости операций, оно изменит только *относительную* стоимость операций. Предсавь тебе на работе выдали ноутбук размером с холодильник и сказали ходить с ним, ты говоришь "да вы поглядите, он же огромный", а тебе предлагают "а ты погляди на него с крыши высотного здания. видишь, отсюда, он не такой уж и большой". Ты предлагаешь примерно то же самое.
БП>Резюмируя вынужден заключить, что если вы не разрабатываете сверхбыстрый менеджер памяти (вот для них и добавлен vgetcpu) или что-то навроде, то вы всё-таки занимаетесь быдлокодерством. Для поддержки некоторого счётчика в многопоточной среде таких наворотов не требуется.
Я бы не стал заявлять так категорично и безоговорочно. Я безусловно согласен, что во *многих* случаях этого не требуется. Но я же не заставляю всех применять такие техники, я просто споросил, не лежитли где в TIB/TEB/CONTEXT хинт текущего процессора. Свои случаи я в силах измерить вдоль и поперёк, что бы заключить нужно ли мне применять такие техники или нет.
Здравствуйте, rus blood, Вы писали:
RB>Здравствуйте, remark, Вы писали:
R>>Отвечаю сразу всем. А именно: Andrew.W Worobow, IID, Геннадий Майко.
RB>Посмотри здесь.
Здравствуйте, remark, Вы писали:
R>2. С помощью инструкции CPUID получать номер APIC ID, по статической таблице переводить его в номер процессора. Это будет работать на всех версиях Windows. Практически то, что нужно, мне даже переводить в номер процессора не нужно, и так устроит, главное иметь уникальное число для каждого процессора. R>Но тут полностью не устраивает, что на моём компьютере CPUID с EAX=1 выполняется 284 такта (полностью сериализует конвеер и выполняет полный барьер памяти).
Идея следующая. Номер процессора получается с помощью cpuid. Но из-за дороговизны он кэшируется. Обновление кэша происходит при выполнении одного из двух условий. Первое — не реже, чем раз в N считываний. Второе — при изменении времени (того, что получается с помощью GetTickCount()).
N тут параметр для твикинга. У меня получается, при N = 5 функция работает в среднем 73 такта, при N = 10 — 40 тактов, при N = 20 — 24 такта.
Точность такого кэширования пока под вопросом. Скорее всего точность будет зависеть от нагрузки. Т.е. можно создать тест, где точность будет близка к 100%, а можно, где будет < 100%. Про "общий случай" пока сказать сложно. Но если эта функция вызывается часто, то точность должна быть приемлемой. Если же вызывается редко, то какая разница, какая у неё точность... я так думаю.
Здравствуйте, remark, Вы писали:
R>Здравствуйте, remark, Вы писали:
R>>2. С помощью инструкции CPUID получать номер APIC ID, по статической таблице переводить его в номер процессора. Это будет работать на всех версиях Windows. Практически то, что нужно, мне даже переводить в номер процессора не нужно, и так устроит, главное иметь уникальное число для каждого процессора. R>>Но тут полностью не устраивает, что на моём компьютере CPUID с EAX=1 выполняется 284 такта (полностью сериализует конвеер и выполняет полный барьер памяти).
R>У меня получился следующий набросок:
Возможно эта функция не будет различать разные аппаратные потоки на процессорах с HT. Но мне лично в принципе это и не нужно. Так даже может и лучше будет.
Здравствуйте, remark, Вы писали:
R>я просто споросил, не лежитли где в TIB/TEB/CONTEXT хинт текущего процессора.
Нет не лежит. В том смысле, что его туда некому класть. Похоже что тут
ответ правильный.
Есть слабая надежда на SP3 для XP, но для Win2k надежды никакой.
ИМХО, особой беды тут нет — вероятность что на новой машине будет стоять win2k довольно мала, а старые в основном однопроцессорные и одноядерные.
Тем, кто будет жаловаться на недостаточную масштабируемость вашего проиложения можно смело советовать апгрейд.
Здравствуйте, remark, Вы писали:
R>Возможно эта функция не будет различать разные аппаратные потоки на процессорах с HT. Но мне лично в принципе это и не нужно. Так даже может и лучше будет.
Тесты показали что пример работает с ГН.
Здравствуйте, remark, Вы писали:
БП>>имеем забавную картину: всё процессоры непрерывно ломятся в один и тот же адрес пямяти. БП>>По определению, они могут это делать только по очереди, иначе бы терялся весь смысл InterlockedIncrement'а. БП>>Отсюда и масштабируемость 1/кол-во процессоров.
R>Отсюда бы следовала масштабируемость 1, а не 1/N.
В твоем примере небольшой обман. В одном случае 32 потока работают с одной переменной. В другом — размазаны на 4-е. Дополнительный эффект торможения "достигается" из-за бОльшей "нагрузки" поток/переменная.
Неплохо было бы показать, что GetCurrentProcessNumber — лучший способ распределения потоков по переменным. Надо сравнить с другими вариантами распределения:
— жесткая равномерная привязка потоков к переменным
— случайное распределение (индексы можно нагенерировать заранее)
— что-то типа "(c + i) % processor_number", где c — свой для каждого потока.
—
Просто интересно...
ЗЫ
Сорри за cpuid — за разразившейся микровойной захотелось помочь, забыл проверить исходный пост...
Здравствуйте, rus blood, Вы писали:
RB>Здравствуйте, remark, Вы писали:
БП>>>имеем забавную картину: всё процессоры непрерывно ломятся в один и тот же адрес пямяти. БП>>>По определению, они могут это делать только по очереди, иначе бы терялся весь смысл InterlockedIncrement'а. БП>>>Отсюда и масштабируемость 1/кол-во процессоров.
R>>Отсюда бы следовала масштабируемость 1, а не 1/N.
RB>В твоем примере небольшой обман. В одном случае 32 потока работают с одной переменной. В другом — размазаны на 4-е. Дополнительный эффект торможения "достигается" из-за бОльшей "нагрузки" поток/переменная.
Ну в принципе, согласен. Более честно было бы сравнивать работу с 4 переменными с помощью GetCurrentProcessNumber(), в другом с помощью какого-то другого распределения.
RB>Неплохо было бы показать, что GetCurrentProcessNumber — лучший способ распределения потоков по переменным. Надо сравнить с другими вариантами распределения: RB>- жесткая равномерная привязка потоков к переменным RB>- случайное распределение (индексы можно нагенерировать заранее) RB>- что-то типа "(c + i) % processor_number", где c — свой для каждого потока. RB>-
Попробую протестить — просто не всегда есть время на это.
Хотя я уверен, что результат будет в пользу GetCurrentProcessNumber(), хотя, конечно, распределение нагрузки на 4 переменных увеличит масштабируемость.
Здравствуйте, Блудов Павел, Вы писали:
БП>Здравствуйте, remark, Вы писали:
R>>я просто споросил, не лежитли где в TIB/TEB/CONTEXT хинт текущего процессора. БП>Нет не лежит. В том смысле, что его туда некому класть.
В смысле не лежит под XP или не лежит вообще?
Я себе представляю это так. Шедулеру после выбора потока на исполнение ничего не стоит записать вместе с локальными данным потока так же и номер процессора. Шедулер всегда знает номер процессора на который он направляет на исполнение, т.ч. это просто одна локальная запись — практически бесплатно. Я бы по крайней мере сделал бы так.
Не понимаю, почему они делают это таким извращенным способом через инструкцию lsl. Выглядит как затычка. Неужели они не могут расширять информацию хранимую, в [fs].
ответ правильный. БП>Есть слабая надежда на SP3 для XP, но для Win2k надежды никакой. БП>ИМХО, особой беды тут нет — вероятность что на новой машине будет стоять win2k довольно мала, а старые в основном однопроцессорные и одноядерные. БП>Тем, кто будет жаловаться на недостаточную масштабируемость вашего проиложения можно смело советовать апгрейд.
В принципе — да, но не приятно включать и описывать такие артефакты...
Тем более похоже, что амортизированный вариант с cpuid скорее всего будет работать. Похоже, что функция vgetcpu() работает примерно таким же образом. Хотя не понятно как они с частой jiffies 100Hz добились точности...
Здравствуйте, remark, Вы писали:
R>В смысле не лежит под XP или не лежит вообще?
Не лежит под XP. Шедулер от XP её не помещает в доступный из user mode участок памяти.
Здравствуйте, remark, Вы писали:
R>Я себе представляю это так. Шедулеру после выбора потока на исполнение ничего не стоит записать вместе с локальными данным потока так же и номер процессора. Шедулер всегда знает номер процессора на который он направляет на исполнение, т.ч. это просто одна локальная запись — практически бесплатно. Я бы по крайней мере сделал бы так. R>Не понимаю, почему они делают это таким извращенным способом через инструкцию lsl. Выглядит как затычка. Неужели они не могут расширять информацию хранимую, в [fs].
Если речь идет про WINDOWS, то кто такой шедуллер?
а начальный адрес этого массива попадает на границу линейки кеша? Иначе результаты могут не совсем достоверные. Конечно, компилятор должен был бы постараться — но не факт . Если речь идет про VC, было бы наверное правильно в описании структуры padded_value добавить __declspec(align(128))
Здравствуйте, remark, Вы писали:
R>Как получить функциональность GetCurrentProcessorNumber()/NtGetCurrentProcessorNumber() под WinXP/2k?
На AMD64 есть инструкция RDTSCP (Read Time-Stamp Counter and Processor ID), которая, помимо TS счётчика, возвращает так же идентификатор процессора.
Ложку дёгтя добавляет факт, что RDTSCP, в отличие от оригинальной RDTSC, сериализует выполнение конвейера, т.е. ждёт retirement'а всех предыдущих инструкций. Смысл в общем-то понятен — сделать более удобным и быстрым замер времени выполнения участков кода. Но блин, сделали бы возможность отдельно получать только Processor ID, и без сериализации, тогда бы и северный мост не нагружался.
А так вообще latecy: 41 core clocks + 16 Northbridge clocks.
Здравствуйте, remark, Вы писали:
R>Идея следующая. Номер процессора получается с помощью cpuid. Но из-за дороговизны он кэшируется. Обновление кэша происходит при выполнении одного из двух условий. Первое — не реже, чем раз в N считываний. Второе — при изменении времени (того, что получается с помощью GetTickCount()).
Странные условия.
Ни то, ни другое не имеет отношения к перепланировке потоков.
Время в shared_user_data может как измениться во время исполнения потока, так может и не измениться после переключения с потока на другой и обратно.
Здравствуйте, rus blood, Вы писали:
RB>Здравствуйте, remark, Вы писали:
R>>Идея следующая. Номер процессора получается с помощью cpuid. Но из-за дороговизны он кэшируется. Обновление кэша происходит при выполнении одного из двух условий. Первое — не реже, чем раз в N считываний. Второе — при изменении времени (того, что получается с помощью GetTickCount()).
RB>Странные условия. RB>Ни то, ни другое не имеет отношения к перепланировке потоков. RB>Время в shared_user_data может как измениться во время исполнения потока, так может и не измениться после переключения с потока на другой и обратно.
Я с радостью готов выслушать любые предложения по улучшению алгоритма.
Какой вариант ты можешь предложить?
Она впринципе не совсем справляется с заявленными целями, поскольку у каждого ядра своя IDT. Но для идентификации процессора по-моему подойти должна
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
#include <windows.h>
#include <conio.h>
#include <stdio.h>
unsigned get_idt_base()
{
#pragma pack(push, 1)
struct { WORD limit; DWORD base; } idt;
#pragma pack(pop)
__asm lea ecx, idt
__asm sidt [ecx]
return idt.base;
}
int main()
{
for ( unsigned cpu = 0; SetThreadAffinityMask(GetCurrentThread(), 1 << cpu); ++cpu )
{
printf("CPU#%u IDT base is %x\n", cpu, get_idt_base());
}
_getch();
}
ЗЫ начиная с MSVC 2005 SP1 есть интринсик __sidt
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Сорри за задержку — не было времени разобраться и проверить.
Выглядит очень круто!
На последних AMD, согласно документации выполняется 7 тактов. На Intel документации не нашёл, но на тех, на которых проверил, тоже порядка 7 тактов. Конвеер не сериализует.
Хммм... Странно, что я нигде не нашёл про неё. Вопрос неоднократно поднимался в USENET...
На многопроцессорных/многоядерных WinXP IDT действительно разная на каждый процессор. А есть какие-нибудь гарантии на этот счёт? Чем это обусловлено?
Ну хотя наврядли это уже измениться на Win32, Win2k. А на Viste уже есть GetCurrentProcessorNumber().
Здравствуйте, remark, Вы писали:
R>На многопроцессорных/многоядерных WinXP IDT действительно разная на каждый процессор. А есть какие-нибудь гарантии на этот счёт? Чем это обусловлено?
Соотв. функция в WDK позволяет регистрировать ISR для произвольного поцессора:
The IoConnectInterrupt routine registers a device driver's InterruptService routine (ISR), so that it will be called when a device interrupts on any of a specified set of processors.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth