Здравствуйте, remark, Вы писали:
R>О LB я упомянул только, что бы сказать, что нельзя вот так просто сделать SetThreadAffinityMask(), это компромисное решение. Помимо возможности определять номер процессора ты получишь ещё ряд артефактов, которые скорее всего ты не хочешь иметь. Больше никоим образом меня сейчас LB не интересует.
Можно более мягко попросить планировщик выбирать предпочитаемый процессор для исполнения кода потока — SetThreadIdealProcessor. Для ваших целей оптимизации чего-то там вполне может подойти
When a thread becomes ready to run, Windows 2000 first tries to schedule the thread to run on an idle processor. If there is a choice of idle processors, preference is given first to the thread's ideal processor, then to the thread's last processor, and then to the currently executing processor (that is, the CPU on which the scheduling code is running). If none of these CPUs are idle, Windows 2000 picks the first available idle processor by scanning the idle processor mask from highest to lowest CPU number.
If all processors are currently busy and a thread becomes ready, Windows 2000 looks to see whether it can preempt a thread in the running or standby state on one of the CPUs. Which CPU is examined? The first choice is the thread's ideal processor, and the second choice is the thread's last processor. If neither of those CPUs are in the thread's affinity mask, Windows 2000 selects the highest processor in the active processor mask that the thread can run on.
ГМ>>-- ГМ>>В примере Вы сравниваете апельсины с яблоками, потому что результаты работы 2х функций разные.
R>Естественно, 2 разные функции делают разное. Я сравниваю 2 разных метода решения одной и той же задачи. По-моему, тут всё корректно. А сравнивать 2 функции, которые дают одинаковый результат имхо бессмысленно...
--
1. Ваши два разных метода решения одной и той же задачи приводят к разным ответам, поэтому я по прежнему считаю Ваши результаты сравнения некорректными.
2. Почему же бессмысленно сравнивать 2 функции (устройства, методы и т.п.), которые дают один результат? Этот результат-то можно получить разными способами, за разное времы, с различными усилями и т.п.
ГМ>>Если я правильно понял, Вы хотите поместить некоторые данные в кеш процессора и работать только с ними "локально" в этом кэше, нет?
R>Ну, так скажем, примерно. Совсем локально — это уже другой случай, тогда можно просто размещать данные на стеке. Я же хочу "большую часть времени" работать с данными уже находящимися в кэше процессора, но при этом иметь возможность и агрегировать эти данные. R>Т.е. с логической т.з. мне нужны глобальные данные, но с физической т.з. я хочу сделать их локальными.
--
1. Что такое "агрегировать эти данные"?
2. Как только Вы захотите корректно использовать глобальные данные, Вам нужно будет обеспечить механизм их консистентной и корректной видимости для всех CPU, которые захотят их использовать. Вам обязательно понадобится механизм обновления или переноса измененного глобального значения этой переменной от одного CPU к другому. Я бы попытался сравнивать время работу именно этих механизмов, хотя боюсь, это будет не просто. Более того, я бы стал это делать только тогда, когда выяснилось бы, что именно это является bottleneck в моей задаче.
Здравствуйте, Sergey Storozhevykh, Вы писали:
SS>Здравствуйте, remark, Вы писали:
R>>О LB я упомянул только, что бы сказать, что нельзя вот так просто сделать SetThreadAffinityMask(), это компромисное решение. Помимо возможности определять номер процессора ты получишь ещё ряд артефактов, которые скорее всего ты не хочешь иметь. Больше никоим образом меня сейчас LB не интересует.
SS>Можно более мягко попросить планировщик выбирать предпочитаемый процессор для исполнения кода потока — SetThreadIdealProcessor. Для ваших целей оптимизации чего-то там вполне может подойти
Речь идёт о библиотечном коде, т.ч. никакие трюки типа SetThreadIdealProcessor() и SetThreadAffinityMask() не подходят.
ГМ>>>-- ГМ>>>В примере Вы сравниваете апельсины с яблоками, потому что результаты работы 2х функций разные.
R>>Естественно, 2 разные функции делают разное. Я сравниваю 2 разных метода решения одной и той же задачи. По-моему, тут всё корректно. А сравнивать 2 функции, которые дают одинаковый результат имхо бессмысленно... ГМ>-- ГМ>1. Ваши два разных метода решения одной и той же задачи приводят к разным ответам, поэтому я по прежнему считаю Ваши результаты сравнения некорректными. ГМ>2. Почему же бессмысленно сравнивать 2 функции (устройства, методы и т.п.), которые дают один результат? Этот результат-то можно получить разными способами, за разное времы, с различными усилями и т.п.
Если под разным результатами ты имеешь в виду, что в одном случае сумма лежит в одной переменной, а в другом в нескольких, то это не есть разный результат, т.к. я в тест специально включил и агрегирование данных. В одном случае оно выглядит как:
volatile int x = values[0].value;
В другом как:
volatile int x = 0;
for (int j = 0; j != arch::processor_count; ++j)
{
x += values[j].value;
}
Т.ч. в этом смысле результата разного нет — я всегда получаю полную сумму.
ГМ>>>Если я правильно понял, Вы хотите поместить некоторые данные в кеш процессора и работать только с ними "локально" в этом кэше, нет?
R>>Ну, так скажем, примерно. Совсем локально — это уже другой случай, тогда можно просто размещать данные на стеке. Я же хочу "большую часть времени" работать с данными уже находящимися в кэше процессора, но при этом иметь возможность и агрегировать эти данные. R>>Т.е. с логической т.з. мне нужны глобальные данные, но с физической т.з. я хочу сделать их локальными. ГМ>-- ГМ>1. Что такое "агрегировать эти данные"?
Это то, что я привёл выше.
ГМ>2. Как только Вы захотите корректно использовать глобальные данные, Вам нужно будет обеспечить механизм их консистентной и корректной видимости для всех CPU, которые захотят их использовать.
В тесте это обеспечивается.
ГМ>Вам обязательно понадобится механизм обновления или переноса измененного глобального значения этой переменной от одного CPU к другому.
У меня все компьютеры с когерентрым кэшем. Компьютеры с некогерентным кэшем меня пока не интересует... потому что я их не видел.
ГМ>Я бы попытался сравнивать время работу именно этих механизмов, хотя боюсь, это будет не просто.
Всё, что нужно, учитывается в тесте. Это полноценный вариант счётчика.
ГМ>Более того, я бы стал это делать только тогда, когда выяснилось бы, что именно это является bottleneck в моей задаче.
Здравствуйте, remark,
R>Если под разным результатами ты имеешь в виду, что в одном случае сумма лежит в одной переменной, а в другом в нескольких, то это не есть разный результат, т.к. я в тест специально включил и агрегирование данных. В одном случае оно выглядит как: R>
R> volatile int x = values[0].value;
R>
R>В другом как: R>
R> volatile int x = 0;
R> for (int j = 0; j != arch::processor_count; ++j)
R> {
R> x += values[j].value;
R> }
R>
R>Т.ч. в этом смысле результата разного нет — я всегда получаю полную сумму.
--
Замените arch::processor_count на случайную величину, и я сомневаюсь, что результаты такого теста будут сильно отличаться от Вашего оригинального.
ГМ>>Вам обязательно понадобится механизм обновления или переноса измененного глобального значения этой переменной от одного CPU к другому.
R>У меня все компьютеры с когерентрым кэшем. Компьютеры с некогерентным кэшем меня пока не интересует... потому что я их не видел.
ГМ>>Я бы попытался сравнивать время работу именно этих механизмов, хотя боюсь, это будет не просто.
R>Всё, что нужно, учитывается в тесте. Это полноценный вариант счётчика.
--
Да, я тоже имею в виду компьютеры с когерентым кешем. Я просто хочу Вам подсказать, что вариантов обеспечения такой когерентности может быть не один. Посмотрите, например, описания тех же x86 процессоров.
ГМ>>Более того, я бы стал это делать только тогда, когда выяснилось бы, что именно это является bottleneck в моей задаче.
R>Я отказываюсь это обсуждать — с этим в Философию.
--
Да, наверное, на этом стоит прекратить нашу дискуссию.
Здравствуйте, Геннадий Майко, Вы писали:
R>>Т.ч. в этом смысле результата разного нет — я всегда получаю полную сумму. ГМ>-- ГМ>Замените arch::processor_count на случайную величину, и я сомневаюсь, что результаты такого теста будут сильно отличаться от Вашего оригинального.
Какой в этом смысл?
Эта величина не должна быть случайной!
R>>Всё, что нужно, учитывается в тесте. Это полноценный вариант счётчика. ГМ>-- ГМ>Да, я тоже имею в виду компьютеры с когерентым кешем. Я просто хочу Вам подсказать, что вариантов обеспечения такой когерентности может быть не один. Посмотрите, например, описания тех же x86 процессоров.
А какая разница какой механизм обеспечения когерентности используется? Главное, что он есть.
Я именно под x86 в основном и пишу...
Здравствуйте, remark,
R>>>Т.ч. в этом смысле результата разного нет — я всегда получаю полную сумму. ГМ>>-- ГМ>>Замените arch::processor_count на случайную величину, и я сомневаюсь, что результаты такого теста будут сильно отличаться от Вашего оригинального.
R>Какой в этом смысл? R>Эта величина не должна быть случайной!
--
Смысл простой — предпочтительно работать с разными переменными в разных потоках.
А какая разница — случайная ли она из-за вызова функции GetCurrentProcessorNumber() или или из-за вызова функции rand()?
Если Ваша цель — как-то разделить обработку переменной, то это можно делать не только по номеру процессора, но и по любому другому закону; главное, чтобы обеспечивалось уникальность номера для каждого потока команд.
Правила форума нарушены.
— оверквотинг
Правила можно найти в разделе FAQ данного форума и\или ресурса.
Нарушение правил может повлечь за собой санкции, описанные там же — модератор
ГМ>Если Ваша цель — как-то разделить обработку переменной, то это можно делать не только по номеру процессора, но и по любому другому закону; главное, чтобы обеспечивалось уникальность номера для каждого потока команд.
Здравствуйте, Геннадий Майко, Вы писали:
ГМ>Well, я, пока не знаю точно, что подразумевается под выделенным выше, но если это реализация, например, какого-то рекурсивного спинлока, то вполне можно обойтись и без номера процессора. Я вспоминаю, что эту же тему мы обсуждали как-то несколько лет назад с Евгением Музыченко в другом месте, и вроде пришли к этому же выводу.
Да, я тогда заменил KeGetCurrentProcessorNumber на KeGetCurrentThread, и не только получил всю нужную функциональность, но и избавился от лишних проверок, ибо номер процессора может меняться, а идентификатор потока — нет.
Только вот возникает ощущение, что рекурсивные спинлоки — штука удобная и универсальная, но провоцирует на избыточный код, и порой довольно заметно снижает быстродействие
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Здравствуйте, Геннадий Майко, Вы писали:
ГМ>>Well, я, пока не знаю точно, что подразумевается под выделенным выше, но если это реализация, например, какого-то рекурсивного спинлока, то вполне можно обойтись и без номера процессора. Я вспоминаю, что эту же тему мы обсуждали как-то несколько лет назад с Евгением Музыченко в другом месте, и вроде пришли к этому же выводу.
ЕМ>Да, я тогда заменил KeGetCurrentProcessorNumber на KeGetCurrentThread, и не только получил всю нужную функциональность, но и избавился от лишних проверок, ибо номер процессора может меняться, а идентификатор потока — нет.
Это значит, что функциональность, которая тебе требовалась, прямолинейнее той, которая требуется мне. Про то, что можно использовать идентификатор потока, я, естественно, в курсе. Про то, что его не всегда достаточно я писал здесь: http://gzip.rsdn.ru/forum/message/2806303.1.aspx
ЕМ>Только вот возникает ощущение, что рекурсивные спинлоки — штука удобная и универсальная, но провоцирует на избыточный код, и порой довольно заметно снижает быстродействие
Хммм, вообще считается, что рекурсивные локи — вещь вредная, т.к. провоцирует на небрежный/кривой код. В любом случае, они тривиально реализуются с помощью GetСurrentThreadId(), идентификатор процессора тут совершенно не нужен.
Здравствуйте, remark, Вы писали:
R>Это значит, что функциональность, которая тебе требовалась, прямолинейнее той, которая требуется мне. Про то, что можно использовать идентификатор потока, я, естественно, в курсе. Про то, что его не всегда достаточно я писал здесь: R>http://gzip.rsdn.ru/forum/message/2806303.1.aspx
Я, честно говоря, так и не понял, чего ты хочешь достичь — практически, а не теоретически. Любопытства ради я прогнал твой пример на двухъядерном Athlon 64 X2, под виндами (линуха у меня нет). Создавал восемь потоков с привязкой к процессорам и без нее. Скомпилировано MS VS 2005 (VC 8), _InterlockedIncrement раскрывается в lock xadd. Ну и во втором случае _InterlockedIncrement будет избыточным, ибо конкуренции за переменную нет.
Получилось вот что (количество тактов на проход цикла):
1 с привязкой к процессорам — 56
1 без привязки — 57
2 с привязкой с _InterlockedIncrement — 12
2 без привязки с _InterlockedIncrement — 12
2 с привязкой с ++ — 5
2 без привязки с ++ — 6
То есть, наиболее эффективный вариант — привязывать потоки к процессорам. Если каждый поток занимается интенсивной вычислительной работой, то от привязки к разным процессорам они только выиграют. Если же загрузка неравномерная, то и вероятность конфликта за переменную падает многократно, а вероятность получения номера на одном процессоре и последующего переключения на другой, наоборот, возрастает. Так что обоснованность твоего стремления представляется весьма сомнительной.
Если есть практические примеры, показывающие эффективность именно выбранного тобой подхода — буду рад посмотреть.
ЕМ>Я, честно говоря, так и не понял, чего ты хочешь достичь — практически, а не теоретически.
По-моему товарищ remark хочет использовать/повторить идею, которая реализована в windows и linux при подсчете статистики обращений к диску/тому.
Так, если посмотреть на реализацию FAT, то можно увидеть, что драйвер создает столько экземпляров FILE_SYSTEM_STATISTICS, сколько процессоров и во время работы заполняет их так:
При запросе FSCTL_FILESYSTEM_GET_STATISTICS драйвер не делает никаких сложений, а просто копирует все структуры в выходной буффер, оставляя сложение нужных полей на откуп вызывающему коду.
Похожий подход и в linux (include/linux/genhd.h):
struct gendisk {
....
#ifdef CONFIG_SMP
struct disk_stats *dkstats;
#else
struct disk_stats dkstats;
#endif
};
#ifdef CONFIG_SMP
// Обновление статистики#define disk_stat_add(gendiskp, field, addnd) \
do { \
preempt_disable(); \
// Макрос __disk_stat_add раскрыт \
per_cpu_ptr(gendiskp->dkstats, smp_processor_id())->field += addnd;\
preempt_enable(); \
} while (0)
// Чтение итоговой статистики, например для вывода в файл /sys/block/<dev>/stat#define disk_stat_read(gendiskp, field) \
({ \
typeof(gendiskp->dkstats->field) res = 0; \
int i; \
for_each_possible_cpu(i) \
res += per_cpu_ptr(gendiskp->dkstats, i)->field; \
res; \
})
#endif
Здравствуйте, MShura, Вы писали:
MS>идею, которая реализована в windows и linux при подсчете статистики обращений к диску/тому. MS>Так, если посмотреть на реализацию FAT, то можно увидеть, что драйвер создает столько экземпляров FILE_SYSTEM_STATISTICS
Э-э-э... А какой-нибудь объективный смысл в таком подходе есть, или это сугубо произвольная фантазия разработчиков? О чем может говорить статистика обращений того или иного процессора к дискам, и какую практическую пользу можно из нее извлечь?
Здравствуйте, MShura,
MS>По-моему товарищ remark хочет использовать/повторить идею, которая реализована в windows и linux при подсчете статистики обращений к диску/тому.
MS>[/ccode] MS>#ifdef CONFIG_SMP MS>// Обновление статистики MS>#define disk_stat_add(gendiskp, field, addnd) \ MS> do { \ MS> preempt_disable(); \ MS> // Макрос __disk_stat_add раскрыт \ MS> per_cpu_ptr(gendiskp->dkstats, smp_processor_id())->field += addnd;\ MS> preempt_enable(); \ MS> } while (0) MS>[/ccode]
--
Кстати, я не знаю, как там в Windows, но в linux использование идентификатора процессора делается достаточно корректно, так как на момент изменения переменной запрещается смена процессора.
MS>>идею, которая реализована в windows и linux при подсчете статистики обращений к диску/тому. MS>>Так, если посмотреть на реализацию FAT, то можно увидеть, что драйвер создает столько экземпляров FILE_SYSTEM_STATISTICS
ЕМ>Э-э-э... А какой-нибудь объективный смысл в таком подходе есть, или это сугубо произвольная фантазия разработчиков? О чем может говорить статистика обращений того или иного процессора к дискам, и какую практическую пользу можно из нее извлечь?
Даже не могу сказать про практическую пользу знаний статистики по отдельным процессорам, к тому-же в конкретной файловой системе, но в сумме смысл уже есть.
Так в windows task manager может показывать статистику обращений к диску (не к тому).
В linux можно из этих знаний даже извлечь практическую пользу:
Так в файле /sys/block/<dev>/stat выводится следующая статистика по диску на текущий момент:
Name units description
---- ----- -----------
read I/Os requests number of read I/Os processed
read merges requests number of read I/Os merged with in-queue I/O
read sectors sectors number of sectors read
read ticks milliseconds total wait time for read requests
write I/Os requests number of write I/Os processed
write merges requests number of write I/Os merged with in-queue I/O
write sectors sectors number of sectors written
write ticks milliseconds total wait time for write requests
in_flight requests number of I/Os currently in flight
io_ticks milliseconds total time this block device has been active
time_in_queue milliseconds total wait time for all requests
Поскольку планировщик ввода-вывода (elevator) в linux можно менять на лету, а также менять его параметры, то можно быстро (без перегрузок) оценивать эффективность того или иного элеватора на конкретных файловых операциях или на том или ином сценарии действий или на том или ином носителе.
Справедливости ради стоит сказать, что я не увидел особой разницы в элеваторах deadline/noop/as в обычных условиях на embedded устройствах.
А проводить сложные эксперименты/измерения ради интереса было лень.
ГМ>Кстати, я не знаю, как там в Windows, но в linux использование идентификатора процессора делается достаточно корректно, так как на момент изменения переменной запрещается смена процессора.
Макросы CollectCreateHitStatistics/CollectCreateStatistics
вызываются внутри блока FsRtlEnterFileSystem/FsRtlExitFileSystem
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Здравствуйте, remark, Вы писали:
R>>Это значит, что функциональность, которая тебе требовалась, прямолинейнее той, которая требуется мне. Про то, что можно использовать идентификатор потока, я, естественно, в курсе. Про то, что его не всегда достаточно я писал здесь: R>>http://gzip.rsdn.ru/forum/message/2806303.1.aspx
ЕМ>Я, честно говоря, так и не понял, чего ты хочешь достичь — практически, а не теоретически. Любопытства ради я прогнал твой пример на двухъядерном Athlon 64 X2, под виндами (линуха у меня нет). Создавал восемь потоков с привязкой к процессорам и без нее. Скомпилировано MS VS 2005 (VC 8), _InterlockedIncrement раскрывается в lock xadd. Ну и во втором случае _InterlockedIncrement будет избыточным, ибо конкуренции за переменную нет.
Во-втором случае _InterlockedIncrement *не* избыточный, т.к. номер процессора может измениться, это не более, чем хинт. По крайней мере в user-space. _InterlockedIncrement избыточный только в случае жёсткой привязки потоков к процессорам.
ЕМ>Получилось вот что (количество тактов на проход цикла):
ЕМ>1 с привязкой к процессорам — 56 ЕМ>1 без привязки — 57 ЕМ>2 с привязкой с _InterlockedIncrement — 12 ЕМ>2 без привязки с _InterlockedIncrement — 12 ЕМ>2 с привязкой с ++ — 5 ЕМ>2 без привязки с ++ — 6
ЕМ>То есть, наиболее эффективный вариант — привязывать потоки к процессорам. Если каждый поток занимается интенсивной вычислительной работой, то от привязки к разным процессорам они только выиграют. Если же загрузка неравномерная, то и вероятность конфликта за переменную падает многократно, а вероятность получения номера на одном процессоре и последующего переключения на другой, наоборот, возрастает. Так что обоснованность твоего стремления представляется весьма сомнительной.
Про то, что привязка не всегда возможно я уже писал и писал причины. Допустим ты делаешь библиотечный код. НУ НЕ МОЖЕШЬ ТЫ ПРИВЯЗЫВАТЬ ПОЛЬЗОВАТЕЛЬСКИЕ ПОТОКИ К ПРОЦЕССОРАМ. НЕ МОЖЕШЬ!!!
Учитывая 2, указанные мной причины, получаем:
ЕМ>1 без привязки — 57 ЕМ>2 без привязки с _InterlockedIncrement — 12
Я вижу, что эти 2 цифры различаются в 4,75 раза. Что отсюда не очевидно? И дело не только голой производительности, более серьёзно дело обстоит с масштабируемостью. Если провести более исчерпывающее тестирование, то ты можешь получить следующие цифры масштабируемости.
Умная, аккуратная синхронизация, уважающая кэш:
1 ядро — 1
2 ядра — 2
2 процессора — 2
2 процессора по 2 ядра — 4
Тупая синхронизация в лоб:
1 ядро — 1
2 ядра — 0.6
2 процессора — 0.4
2 процессора по 2 ядра — 0.2
Такие цифры я лицезрел неоднократно.
Попробуй потестировать, привязывая несколько потоков только к одному ядру, либо к обоим.
ЕМ>Если есть практические примеры, показывающие эффективность именно выбранного тобой подхода — буду рад посмотреть.
Конкретные алгоритмы я не могу сейчас раскрывать. Но множество примеров можно поглядеть в ядрах ОС, где "копия на процессора" используется достаточно часто. Это могут быть различные мьютексы, сбор статистики, техники типа RCU. Пару примеров привёл MShura.
Здравствуйте, MShura, Вы писали:
ЕМ>>Я, честно говоря, так и не понял, чего ты хочешь достичь — практически, а не теоретически.
MS>По-моему товарищ remark хочет использовать/повторить идею, которая реализована в windows и linux при подсчете статистики обращений к диску/тому.
В частности и это можно повторить. Только в user-space всё равно надо использовать Interlocked операции.
Здравствуйте, Евгений Музыченко, Вы писали:
ЕМ>Здравствуйте, MShura, Вы писали:
MS>>идею, которая реализована в windows и linux при подсчете статистики обращений к диску/тому. MS>>Так, если посмотреть на реализацию FAT, то можно увидеть, что драйвер создает столько экземпляров FILE_SYSTEM_STATISTICS
ЕМ>Э-э-э... А какой-нибудь объективный смысл в таком подходе есть, или это сугубо произвольная фантазия разработчиков? О чем может говорить статистика обращений того или иного процессора к дискам, и какую практическую пользу можно из нее извлечь?
Я думаю, что используется статистика только после сложения. Раздельный подсчёт — это не более чем деталь реализации.
Практическая польза следующая — получить линейно масштабируемую систему, в отличие от линейно деградирующей системы. Для меня это достаточное условие.
Здравствуйте, remark, Вы писали:
R>Допустим ты делаешь библиотечный код. НУ НЕ МОЖЕШЬ ТЫ ПРИВЯЗЫВАТЬ ПОЛЬЗОВАТЕЛЬСКИЕ ПОТОКИ К ПРОЦЕССОРАМ. НЕ МОЖЕШЬ!!!
А если спокойно подумать? Что за библиотечный код, который, не зная о процессорах и потоках, тем не менее претендует на предельно высокое быстродействие? Это извращение, а не код. Либо библиотека сама создает потоки по определенной схеме, либо требует этого от кода, ее использующего. Либо, соответственно, ничего этого не делает, и на эффективность не претендует.
R>Я вижу, что эти 2 цифры различаются в 4,75 раза. Что отсюда не очевидно?
Отсюда не очевидно, зачем нужно применять на практике этот странный вариант, когда банальное распределение по процессорам дает выигрыш еще в два раза.
ЕМ>>Если есть практические примеры, показывающие эффективность именно выбранного тобой подхода — буду рад посмотреть.
R>Конкретные алгоритмы я не могу сейчас раскрывать.
Начинается... Каким бы новаторским и секретным ни был алгоритм — нет проблем его переиначить или с ходу придумать аналогию, годную для примера.
R> Но множество примеров можно поглядеть в ядрах ОС, где "копия на процессора" используется достаточно часто. Это могут быть различные мьютексы, сбор статистики, техники типа RCU. Пару примеров привёл MShura.
В ядрах ОС понятие "номер процессора" имеет вполне конкретное значение, в отличие от user-mode. Оттого и не возникало реальной необходимости в функции GetCurrentProcessorNumber.