GetCurrentProcessorNumber() под WinXP
От: remark Россия http://www.1024cores.net/
Дата: 20.01.08 16:33
Оценка:
Как получить функциональность GetCurrentProcessorNumber()/NtGetCurrentProcessorNumber() под WinXP/2k?

Поиск дал следующие результаты.
1.
__declspec(naked) unsigned get_current_proc()
{
    __asm
    {
        mov ecx, 03Bh
        lsl eax, ecx
        shr eax, 0Eh
        retn
    }
}

Но как я понял, это будет работать только на тех версиях Windows, где работает и GetCurrentProcessorNumber(). Т.е. не будет работать под WinXP/2k.

2. С помощью инструкции CPUID получать номер APIC ID, по статической таблице переводить его в номер процессора. Это будет работать на всех версиях Windows. Практически то, что нужно, мне даже переводить в номер процессора не нужно, и так устроит, главное иметь уникальное число для каждого процессора.
Но тут полностью не устраивает, что на моём компьютере CPUID с EAX=1 выполняется 284 такта (полностью сериализует конвеер и выполняет полный барьер памяти).


В идеале хотелось чего-то вроде получения идентификатора текущего потока из блока информации потока.


з.ы. Не уверен, куда это больше — к WinAPI или Низкоуровнему программированию, поэтому пока закросспостю в оба форума. Если что можно из одного удалить.



1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: GetCurrentProcessorNumber() под WinXP
От: IID Россия  
Дата: 20.01.08 18:46
Оценка:
Здравствуйте, remark, Вы писали:

а если сразу после получение номера процессора поток будет перепланирован на другой процессор ?
kalsarikännit
Re[2]: GetCurrentProcessorNumber() под WinXP
От: Unmanaged Россия ICQ 476611995
Дата: 20.01.08 18:51
Оценка: +1
IID>а если сразу после получение номера процессора поток будет перепланирован на другой процессор ?

И что?
STATUS_INVALID_DEVICE_REQUEST
Re[2]: GetCurrentProcessorNumber() под WinXP
От: remark Россия http://www.1024cores.net/
Дата: 20.01.08 18:54
Оценка:
Здравствуйте, IID, Вы писали:

IID>Здравствуйте, remark, Вы писали:


IID>а если сразу после получение номера процессора поток будет перепланирован на другой процессор ?


Могу предположить, что тогда поток будет выполняться на другом процессоре.


1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: GetCurrentProcessorNumber() под WinXP
От: IID Россия  
Дата: 20.01.08 19:18
Оценка:
Здравствуйте, remark, Вы писали:

R>Здравствуйте, IID, Вы писали:


IID>>Здравствуйте, remark, Вы писали:


IID>>а если сразу после получение номера процессора поток будет перепланирован на другой процессор ?


R>Могу предположить, что тогда поток будет выполняться на другом процессоре.


R>


а какой тогда смысл в полученном номере ?
kalsarikännit
Re[4]: GetCurrentProcessorNumber() под WinXP
От: Unmanaged Россия ICQ 476611995
Дата: 20.01.08 19:44
Оценка:
IID>а какой тогда смысл в полученном номере ?

Смысл банальный — что в момент получения номера номер был правильный.
Это именно то, что требуется узнать, а именно — "номер в момент получения", а не "номер миллисекундой позже", — чувствуете разницу?
STATUS_INVALID_DEVICE_REQUEST
Re[4]: GetCurrentProcessorNumber() под WinXP
От: remark Россия http://www.1024cores.net/
Дата: 20.01.08 19:54
Оценка:
Здравствуйте, IID, Вы писали:

IID>Здравствуйте, remark, Вы писали:


R>>Здравствуйте, IID, Вы писали:


IID>>>Здравствуйте, remark, Вы писали:


IID>>>а если сразу после получение номера процессора поток будет перепланирован на другой процессор ?


R>>Могу предположить, что тогда поток будет выполняться на другом процессоре.


IID>а какой тогда смысл в полученном номере ?


А какой смысл был бы в противном случае?


Если без шуток и это действительно интересно, то это значение можно охарактерировать как "хинт текущего процессора правильный в большинстве случаев и некоторый промежуток времени после выполнения функции". И именно с этой стороны его можно и нужно использовать. В реальности этот хинт обычно используется для контроля работы примитивов синхронизации, либо для оптимизации примитивов синхронизации.
За то, что он всё же нужен на практике говорит тот факт, что эту функция добавлена в WinAPI и не так давно в Linux.


R>>


1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[5]: GetCurrentProcessorNumber() под WinXP
От: Аноним  
Дата: 20.01.08 20:53
Оценка:
U>Это именно то, что требуется узнать, а именно — "номер в момент получения", а не "номер миллисекундой позже", — чувствуете разницу?
а момент получения — это когда? до вызова call, после call? точно посередке вызывааемой функции? перед ret? после ret?
Re[5]: GetCurrentProcessorNumber() под WinXP
От: IID Россия  
Дата: 20.01.08 21:00
Оценка:
Здравствуйте, remark, Вы писали:


R>Если без шуток и это действительно интересно, то это значение можно охарактерировать как "хинт текущего процессора правильный в большинстве случаев и некоторый промежуток времени после выполнения функции".


Если только как отправную точку для сбора некоторой статистики, для которой не нужна абсолютная точность (см. выделенное), то да... Но опираться на полученное число, например, для организации логики какого-то своего примитива синхронизации, ИМХО неверно. Одно переключение между получением и использованием — и приплыли.
kalsarikännit
Re[6]: GetCurrentProcessorNumber() под WinXP
От: Unmanaged Россия ICQ 476611995
Дата: 20.01.08 21:09
Оценка: 1 (1)
U>>Это именно то, что требуется узнать, а именно — "номер в момент получения", а не "номер миллисекундой позже", — чувствуете разницу?
А>а момент получения — это когда? до вызова call, после call? точно посередке вызывааемой функции? перед ret? после ret?

А это как вам больше нравится .
STATUS_INVALID_DEVICE_REQUEST
Re[6]: GetCurrentProcessorNumber() под WinXP
От: Pzz Россия https://github.com/alexpevzner
Дата: 20.01.08 21:24
Оценка: 1 (1)
Здравствуйте, IID, Вы писали:

IID>Если только как отправную точку для сбора некоторой статистики, для которой не нужна абсолютная точность (см. выделенное), то да... Но опираться на полученное число, например, для организации логики какого-то своего примитива синхронизации, ИМХО неверно. Одно переключение между получением и использованием — и приплыли.


Для обеспечения корректности опираться на аккуратность номера текущего процессора неправильно, а для, например, оптимизации, вполне можно.
Re[7]: GetCurrentProcessorNumber() под WinXP
От: Аноним  
Дата: 20.01.08 22:56
Оценка: +1 -2
U>А это как вам больше нравится .
А мне никак не нравится. Такой вызов имеет смысл только на IRQL>=DISPATCH_LEVEL остальное — быдлокодерство, основанное на женской логике.
Re[8]: GetCurrentProcessorNumber() под WinXP
От: remark Россия http://www.1024cores.net/
Дата: 20.01.08 23:20
Оценка: -2
Здравствуйте, Аноним, Вы писали:

U>>А это как вам больше нравится .

А>А мне никак не нравится. Такой вызов имеет смысл только на IRQL>=DISPATCH_LEVEL остальное — быдлокодерство, основанное на женской логике.

Сразу видно — человек не занимался эффективной синхронизацией


1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[8]: GetCurrentProcessorNumber() под WinXP
От: Unmanaged Россия ICQ 476611995
Дата: 20.01.08 23:33
Оценка:
А>...остальное — быдлокодерство, основанное на женской логике.

Интересная фраза, надо запомнить .
STATUS_INVALID_DEVICE_REQUEST
Re[9]: GetCurrentProcessorNumber() под WinXP
От: Andrew.W Worobow https://github.com/Worobow
Дата: 21.01.08 08:12
Оценка: 1 (1) +1
Здравствуйте, remark, Вы писали:


R>Сразу видно — человек не занимался эффективной синхронизацией


IDD прав, и прав на 100%. Не занимайтесь фигней, только если это не ваше хобби. Синхронизации в пользовательском режиме не строят на знании номера текущего процессора. Для общности можете всегда его считать равным одному. Сделайте через дефайн....
Не все кто уехал, предал Россию.
Re[10]: GetCurrentProcessorNumber() под WinXP
От: remark Россия http://www.1024cores.net/
Дата: 21.01.08 17:41
Оценка:
Здравствуйте, Andrew.W Worobow, Вы писали:

AWW>Здравствуйте, remark, Вы писали:


R>>Сразу видно — человек не занимался эффективной синхронизацией


AWW>IDD прав, и прав на 100%.


Могу только сказать, что IDD и те, кто его поддерживают, не разбираются в эффективной синхронизации.
Я, в принципе, не удивлён такому повороту событий, т.к. в каждой ветке, где идёт обсуждение этого вопроса появляются несколько человек, которые начинают говорить, "да это не работает", "да уже в следующем такте", "да это бесполезно", "вам нужен ид потока", "не занимайтесь быдлокодерством":
http://groups.google.com/group/comp.os.linux.development.system/browse_frm/thread/724a00185ad9b82a
http://groups.google.com/group/comp.os.ms-windows.programmer.win32/browse_frm/thread/c428be6003b793a8
http://groups.google.com/group/comp.os.ms-windows.programmer.nt.kernel-mode/browse_frm/thread/e325e216da2ce409
http://groups.google.com/group/microsoft.public.win32.programmer.kernel/browse_frm/thread/20ce7838fcef4606
http://groups.google.com/group/microsoft.public.win32.programmer.kernel/browse_frm/thread/663176629a3ff210
http://gzip.rsdn.ru/forum/message/2804333.aspx
Автор: remark
Дата: 20.01.08

http://gzip.rsdn.ru/forum/message/2804334.aspx
Автор: remark
Дата: 20.01.08


Почему все как огня бояться этой вещи


AWW> Синхронизации в пользовательском режиме не строят на знании номера текущего процессора.


Это аксиома или мантра?


AWW> Для общности можете всегда его считать равным одному. Сделайте через дефайн....


Как back-off, видимо, придётся это использовать на тех платформах, где не соотв. функции.



1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[11]: GetCurrentProcessorNumber() под WinXP
От: remark Россия http://www.1024cores.net/
Дата: 21.01.08 17:42
Оценка:
Здравствуйте, remark, Вы писали:

R>Здравствуйте, Andrew.W Worobow, Вы писали:


AWW>>Здравствуйте, remark, Вы писали:


R>>>Сразу видно — человек не занимался эффективной синхронизацией


AWW>>IDD прав, и прав на 100%.


R>Могу только сказать, что IDD и те, кто его поддерживают, не разбираются в эффективной синхронизации.

R>Почему все как огня бояться этой вещи


Постараюсь в ближайшее время показать реальный пример использования этой функции.


R>


1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[11]: GetCurrentProcessorNumber() под WinXP
От: Andrew.W Worobow https://github.com/Worobow
Дата: 21.01.08 18:15
Оценка: +1
Здравствуйте, remark, Вы писали:

R>>>Сразу видно — человек не занимался эффективной синхронизацией

AWW>>IDD прав, и прав на 100%.
R>Могу только сказать, что IDD и те, кто его поддерживают, не разбираются в эффективной синхронизации.

Да что ты говоришь. Пока что я вижу, что к сожалению у тебя имеются некоторые пробелы в понимании многозадачности. И видимо отсюда и стремление получить то что ненужно.

Попробуй просто описать идею, тогда проще будет тебе обьяснить твою ошибки.

R>Почему все как огня бояться этой вещи


Да никто не боится, просто возможно модель у тебя в голове не соответстует реалиям. Вполне возможно и отсюда стемление получить то что и так есть например.

AWW>> Синхронизации в пользовательском режиме не строят на знании номера текущего процессора.


R>Это аксиома или мантра?


Это просто очевидно. На 100%. Либо афинити маска, либо ничего.
Не все кто уехал, предал Россию.
Re[9]: GetCurrentProcessorNumber() под WinXP
От: remark Россия http://www.1024cores.net/
Дата: 21.01.08 22:38
Оценка:
Здравствуйте, remark, Вы писали:

R>Здравствуйте, Аноним, Вы писали:


U>>>А это как вам больше нравится .

А>>А мне никак не нравится. Такой вызов имеет смысл только на IRQL>=DISPATCH_LEVEL остальное — быдлокодерство, основанное на женской логике.

R>Сразу видно — человек не занимался эффективной синхронизацией


Смысл имеет и то, и другое. Только разный.

R>


1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[12]: GetCurrentProcessorNumber() под WinXP
От: remark Россия http://www.1024cores.net/
Дата: 22.01.08 01:03
Оценка: 99 (8)
Здравствуйте, 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():

кол-во тактов на итерациюмасштабирование
1261
2281.854
3281.785
4283.645
Ну что ж, масштабирование 3.645 на 4-ёх ядрах — это приемлимо. Теперь смотрим первый вариант, без использования GetCurrentProcessorNumber():

кол-во тактов на итерациюмасштабирование
1241
2760.6361
31200.4038
43760.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


1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.