Мы установили две идентичные рабочие станции HP Z840 со следующими спецификациями
- 2 x Xeon E5-2690 v4 @2.60GHz (Turbo Boost ON, HT OFF, всего 28 логических процессоров)
- 32 ГБ памяти DDR4 2400, четырехканальный
и установил обновления для Windows 7 SP1 (x64) и Windows 10 Creators Update (x64) для каждого из них.
Затем мы запустили небольшой тест памяти (код ниже, построенный с помощью VS2015 Update 3, 64-разрядная архитектура), который одновременно выполняет выделение памяти из нескольких потоков.
#include <Windows.h>
#include <vector>
#include <ppl.h>
unsigned __int64 ZQueryPerformanceCounter()
{
unsigned __int64 c;
::QueryPerformanceCounter((LARGE_INTEGER *)&c);
return c;
}
unsigned __int64 ZQueryPerformanceFrequency()
{
unsigned __int64 c;
::QueryPerformanceFrequency((LARGE_INTEGER *)&c);
return c;
}
class CZPerfCounter {
public:
CZPerfCounter() : m_st(ZQueryPerformanceCounter()) {};
void reset() { m_st = ZQueryPerformanceCounter(); };
unsigned __int64 elapsedCount() { return ZQueryPerformanceCounter() - m_st; };
unsigned long elapsedMS() { return (unsigned long)(elapsedCount() * 1000 / m_freq); };
unsigned long elapsedMicroSec() { return (unsigned long)(elapsedCount() * 1000 * 1000 / m_freq); };
static unsigned __int64 frequency() { return m_freq; };
private:
unsigned __int64 m_st;
static unsigned __int64 m_freq;
};
unsigned __int64 CZPerfCounter::m_freq = ZQueryPerformanceFrequency();
int main(int argc, char ** argv)
{
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
int ncpu = sysinfo.dwNumberOfProcessors;
if (argc == 2) {
ncpu = atoi(argv[1]);
}
{
printf("No of threads %d\n", ncpu);
try {
concurrency::Scheduler::ResetDefaultSchedulerPolicy();
int min_threads = 1;
int max_threads = ncpu;
concurrency::SchedulerPolicy policy
(2 // two entries of policy settings
, concurrency::MinConcurrency, min_threads
, concurrency::MaxConcurrency, max_threads
);
concurrency::Scheduler::SetDefaultSchedulerPolicy(policy);
}
catch (concurrency::default_scheduler_exists &) {
printf("Cannot set concurrency runtime scheduler policy (Default scheduler already exists).\n");
}
static int cnt = 100;
static int num_fills = 1;
CZPerfCounter pcTotal;
// malloc/free
printf("malloc/free\n");
{
CZPerfCounter pc;
for (int i = 1 * 1024 * 1024; i <= 8 * 1024 * 1024; i *= 2) {
concurrency::parallel_for(0, 50, [i](size_t x) {
std::vector<void *> ptrs;
ptrs.reserve(cnt);
for (int n = 0; n < cnt; n++) {
auto p = malloc(i);
ptrs.emplace_back(p);
}
for (int x = 0; x < num_fills; x++) {
for (auto p : ptrs) {
memset(p, num_fills, i);
}
}
for (auto p : ptrs) {
free(p);
}
});
printf("size %4d MB, elapsed %8.2f s, \n", i / (1024 * 1024), pc.elapsedMS() / 1000.0);
pc.reset();
}
}
printf("\n");
printf("Total %6.2f s\n", pcTotal.elapsedMS() / 1000.0);
}
return 0;
}
Удивительно, но в Windows 10 CU результат очень плохой, по сравнению с Windows 7. Я построил результат ниже для размера блока 1 МБ и размера блока 8 МБ, изменяя количество потоков от 2,4,.. до 28. Хотя Windows 7 давала немного худшую производительность, когда мы увеличивали количество потоков, Windows 10 давала гораздо худшую масштабируемость.
Мы постарались убедиться, что все обновления Windows применяются, обновляют драйверы, настраивают настройки BIOS без успеха. Мы также использовали тот же бенчмарк на нескольких других аппаратных платформах, и все они дали аналогичную кривую для Windows 10. Так что это проблема Windows 10.
Есть ли у кого-то подобный опыт или, может быть, ноу-хау (возможно, мы что-то пропустили?). Такое поведение заставило наше многопоточное приложение получить значительный успех.
*** EDITED
Используя https://github.com/google/UIforETW (спасибо Брюсу Доусону), чтобы проанализировать бенчмарк, мы обнаружили, что большую часть времени тратится в ядрах KiPageFault. Копая дальше по дереву вызовов, все приводит к ExpWaitForSpinLockExclusiveAndAcquire. Кажется, что проблема блокировки вызывает эту проблему.
*** EDITED
Собранный сервер 2012 R2 данные на одном оборудовании. Сервер 2012 R2 также хуже Win7, но все же намного лучше, чем Win10 CU.
*** EDITED
Это происходит и на сервере 2016. Я добавил тег windows-server-2016.
*** EDITED
Используя информацию из @Ext3h, я изменил эталон для использования VirtualAlloc и VirtualLock. Я могу подтвердить значительное улучшение по сравнению с тем, когда VirtualLock не используется. Общий Win10 по-прежнему на 30-40% медленнее, чем Win7, когда оба используют VirtualAlloc и VirtualLock.