Как я могу программно найти частоту процессора с помощью C

Я пытаюсь выяснить, есть ли вообще какая-либо идея о частоте процессора в системе, на которой работает мой код C.

Чтобы уточнить, я ищу абстрактное решение (которое не будет привязано к определенной архитектуре или ОС), что может дать мне представление о рабочей частоте компьютера, на котором работает мой код. Мне не нужно быть точным, но я хотел бы быть в парке (например, у меня 2,2 ГГц процессор, я бы хотел сказать в своей программе, что я в нескольких сотнях МГц)

Кто-нибудь имеет идею использовать стандартный код C?

Ответ 1

Как вы находите, частота процессора зависит как от архитектуры, так и от ОС, и нет абстрактного решения.

Если бы мы были еще 20 лет назад, и вы использовали ОС без переключения контекста, и CPU выполнил приведенные в нем инструкции, вы могли бы написать код C в цикле и время его, а затем на основе сборки был скомпилирован для вычисления количества инструкций во время выполнения. Это уже делает предположение, что каждая инструкция принимает 1 такт, что является довольно плохим предположением с тех пор, как конвейерные процессоры.

Но любая современная ОС будет переключаться между несколькими процессами. Даже тогда вы можете попытаться выполнить кучу идентичных циклов цикла for (игнорируя время, необходимое для сбоев страницы и несколько других причин, по которым ваш процессор может остановиться) и получить медианное значение.

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

Ответ 2

Можно найти общее решение, которое правильно использует рабочую частоту для одного потока или многих потоков. Для этого не требуются привилегии администратора /root или доступ к реестрам, специфичным для модели. Я тестировал это на Linux и Windows на процессорах Intel, включая Nahalem, Ivy Bridge и Haswell с одним гнездом до четырех сокетов (40 потоков). Результаты отклонены менее чем на 0,5% от правильных ответов. Прежде чем я покажу вам, как это сделать, позвольте мне показать результаты (из GCC 4.9 и MSVC2013):

Linux:    E5-1620 (Ivy Bridge) @ 3.60GHz    
1 thread: 3.789, 4 threads: 3.689 GHz:  (3.8-3.789)/3.8 = 0.3%, 3.7-3.689)/3.7 = 0.3%

Windows:  E5-1620 (Ivy Bridge) @ 3.60GHz
1 thread: 3.792, 4 threads: 3.692 GHz: (3.8-3.789)/3.8 = 0.2%, (3.7-3.689)/3.7 = 0.2%

Linux:  4xE7-4850 (Nahalem) @ 2.00GHz
1 thread: 2.390, 40 threads: 2.125 GHz:, (2.4-2.390)/2.4 = 0.4%, (2.133-2.125)/2.133 = 0.4%

Linux:    i5-4250U (Haswell) CPU @ 1.30GHz
1 thread: within 0.5% of 2.6 GHz, 2 threads wthin 0.5% of 2.3 GHz

Windows: 2xE5-2667 v2 (Ivy Bridge) @ 3.3 GHz
1 thread: 4.000 GHz, 16 threads: 3.601 GHz: (4.0-4.0)/4.0 = 0.0%, (3.6-3.601)/3.6 = 0.0%

У меня возникла идея по этому поводу http://randomascii.wordpress.com/2013/08/06/defective-heat-sinks-causing-garbage-gaming/

Для этого вы сначала делаете то, что делаете 20 лет назад. Вы пишете код с циклом, в котором вы знаете время ожидания и время его. Вот что я использовал:

static int inline SpinALot(int spinCount)
{
    __m128 x = _mm_setzero_ps();
    for(int i=0; i<spinCount; i++) {
        x = _mm_add_ps(x,_mm_set1_ps(1.0f));
    }
    return _mm_cvt_ss2si(x);
}

У этого есть зависимая петля, поэтому CPU не может изменить порядок, чтобы уменьшить задержку. Для каждой итерации всегда требуется 3 такта. ОС не переносит поток в другое ядро, потому что мы будем связывать потоки.

Затем вы запускаете эту функцию на каждом физическом ядре. Я сделал это с помощью OpenMP. Для этого необходимо связать потоки. В linux с GCC вы можете использовать export OMP_PROC_BIND=true для привязки потоков и при условии, что у вас есть ncores физическое ядро, также export OMP_NUM_THREADS=ncores. Если вы хотите программно связать и найти количество физических ядер для процессоров Intel, см. Это programatically-detect-number-of-physical-processors-cores-or-if-hyper-threading и thread-affinity-with-windows-msvc-and-openmp.

void sample_frequency(const int nsamples, const int n, float *max, int nthreads) {
    *max = 0;
    volatile int x = 0;
    double min_time = DBL_MAX;
    #pragma omp parallel reduction(+:x) num_threads(nthreads)
    {
        double dtime, min_time_private = DBL_MAX;
        for(int i=0; i<nsamples; i++) {
             #pragma omp barrier
             dtime = omp_get_wtime();
             x += SpinALot(n);
             dtime = omp_get_wtime() - dtime;
             if(dtime<min_time_private) min_time_private = dtime;
        }
        #pragma omp critical
        {
            if(min_time_private<min_time) min_time = min_time_private;
        }
    }
    *max = 3.0f*n/min_time*1E-9f;
}

Наконец, запустите пробник в цикле и распечатайте результаты

int main(void) {
    int ncores = getNumCores();
    printf("num_threads %d, num_cores %d\n", omp_get_max_threads(), ncores);       
    while(1) {
        float max1, median1, max2, median2;
        sample_frequency(1000, 1000000, &max2, &median2, ncores);
        sample_frequency(1000, 1000000, &max1, &median1,1);          
        printf("1 thread: %.3f, %d threads: %.3f GHz\n" ,max1, ncores, max2);
    }
}

Я не тестировал это на процессорах AMD. Я думаю, что процессоры AMD с модулями (например, Bulldozer) будут привязываться к каждому модулю не каждый "ядро" AMD. Это можно сделать с помощью export GOMP_CPU_AFFINITY с помощью GCC. Вы можете найти полный рабочий пример https://bitbucket.org/zboson/frequency, который работает на Windows и Linux на процессорах Intel и будет правильно определять количество физических ядер для процессоров Intel (по крайней мере с тех пор Nahalem) и связывает их с каждым физическим ядром (без использования OMP_PROC_BIND, который MSVC не имеет).

Ответ 3

Для полноты уже существует простое, быстрое, точное, пользовательское решение с огромным недостатком: оно работает только на Intel Skylake, Kabylake и более новых процессорах. Точным требованием является поддержка уровня CPUID 16h. В соответствии с Руководством Intel Software Developer Guide 325462, выпуск 59, стр. 770:

  • CPUID.16h.EAX = базовая частота процессора (в МГц);

  • CPUID.16h.EBX = Максимальная частота (в МГц);

  • CPUID.16h.ECX = Частота шины (эталонная) (в МГц).

Пример кода Visual Studio 2015:

#include <stdio.h>
#include <intrin.h>

int main(void) {
    int cpuInfo[4] = { 0, 0, 0, 0 };
    __cpuid(cpuInfo, 0);
    if (cpuInfo[0] >= 0x16) {
        __cpuid(cpuInfo, 0x16);

        //Example 1
        //Intel Core i7-6700K Skylake-H/S Family 6 model 94 (506E3)
        //cpuInfo[0] = 0x00000FA0; //= 4000 MHz
        //cpuInfo[1] = 0x00001068; //= 4200 MHz
        //cpuInfo[2] = 0x00000064; //=  100 MHz

        //Example 2
        //Intel Core m3-6Y30 Skylake-U/Y Family 6 model 78 (406E3)
        //cpuInfo[0] = 0x000005DC; //= 1500 MHz
        //cpuInfo[1] = 0x00000898; //= 2200 MHz
        //cpuInfo[2] = 0x00000064; //=  100 MHz

        //Example 3
        //Intel Core i5-7200 Kabylake-U/Y Family 6 model 142 (806E9)
        //cpuInfo[0] = 0x00000A8C; //= 2700 MHz
        //cpuInfo[1] = 0x00000C1C; //= 3100 MHz
        //cpuInfo[2] = 0x00000064; //=  100 MHz

        printf("EAX: 0x%08x EBX: 0x%08x ECX: %08x\r\n", cpuInfo[0], cpuInfo[1], cpuInfo[2]);
        printf("Processor Base Frequency:  %04d MHz\r\n", cpuInfo[0]);
        printf("Maximum Frequency:         %04d MHz\r\n", cpuInfo[1]);
        printf("Bus (Reference) Frequency: %04d MHz\r\n", cpuInfo[2]);
    } else {
        printf("CPUID level 16h unsupported\r\n");
    }
    return 0;
}

Ответ 4

Частота процессора связана с оборудованием, поэтому нет общего метода, который вы можете применить для его получения, это также зависит от используемой ОС.

Например, если вы используете Linux, вы можете прочитать файл /proc/cpuinfo, или вы можете проанализировать журнал загрузки dmesg, чтобы получить это значение, или если вы хотите, чтобы вы могли видеть, как Linux-интерфейс обрабатывает этот материал здесь и пытается настроить код для удовлетворения ваших потребностей:

https://github.com/torvalds/linux/blob/master/arch/x86/kernel/cpu/proc.c

С уважением.

Ответ 5

Я предполагаю, что одним из способов получения тактовой частоты от программного обеспечения является твердое кодирование знаний Справочного руководства по аппаратным средствам (HRM) в программном обеспечении. Вы можете считывать регистры конфигурации часов из программного обеспечения. Предполагая, что вы знаете частоту источника, программное обеспечение может использовать значения множителя и делителя из регистров часов и применять соответствующие формулы, как указано в HRM, для получения тактовой частоты.