Как стать автором
Обновить

Комментарии 41

НЛО прилетело и опубликовало эту надпись здесь
Мое личное пожелание — если сравниваешь GPU-алоритм с «CPU» то, пожалуйста, не используй ".Net" или Java или Питон, даже ученые уже «воют» от ужаса, что современные программисты даже для науки все штампуют на скриптовых языках которые в десятки раз медленнее Си/С++
так код приложенный выполняющих алгоритм мной написан на c++, а C# используется лишь для замера времени работы приложений, формирования отчёта.
даже ученые уже «воют» от ужаса, что современные программисты даже для науки все штампуют на скриптовых языках которые в десятки раз медленнее Си/С++

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

В этом плане тот же питон намного дружелюбней, тем более, что в случае с CUDA на нем пишется только «склеивающий» код, некритичный к производительности.

+
Если Питон использовать по уму с модулями типа numpy то никакого драматического снижения производительности нет

Если Питон использовать по уму с модулями типа numpy то никакого драматического снижения производительности нет

Если С++ использовать по уму, то выстрелов в ногу тоже не будет.

Суть в том, что программист высокого уровня (обученный и с опытом) может написать как быструю программу на Python, так и безопасную программу на C++. А значит, выбор языка будет определяться другими факторами: конкретным сценарием / use case'ом, практиками принятыми в организации и т. п. Преимуществом языков с автоматизацией управления памятью, дополнительными проверками, прозрачной конвертацией типов и т. п. состоит в том, что человек с нуля может уже через небольшое время начать писать программы, которые будут работать. Если же есть возможность нанять человека уже со значительным опытом, то это преимущество нивелируется.
Потому что они позволяют меньше стрелять в ногу и быстрее получать результат?

Да, именно поэтому. Они делают 1000 проверок там, где достаточно 10. Соответственно, приходится выбирать: либо написать программу быстро, либо написать быструю программу.

Потому что, наверное, нормальных исключений нет?

Что ты понимаешь под «нормальными исключениями», и где по твоему мнению их нет?
Они делают 1000 проверок там, где достаточно 10.

Например? С пруфами, желательно.

Соответственно, приходится выбирать: либо написать программу быстро, либо написать быструю программу.

В контексте «написать загрузчик параллельного кода на GPU, который протащит туда данные откуда-то» проблема «написать быструю программу» вообще не стоит. Стоит как правило задача БЫСТРО алгоритмизировать задачу и дать ей вообще возможность пережевать данные параллельно. Силами специалиста-предметника (датасатаниста).
Поэтому — pyCUDA, numpy, pandas — уже написанные на С или С++ — их всё. А питон достаточно выразителен, чтобы эту задачу изложить средствами языка.

Поэтому — где си/кресты — а где научные вычисления.

Что ты понимаешь под «нормальными исключениями», и где по твоему мнению их нет?

Под нормальными исключениями я понимаю наличие конструкций throw, try-except-finally, и нет их в фрагменте кода ниже.
cudaStatus = cudaSetDevice(0);
	if (cudaStatus != cudaSuccess) {
		fprintf(stderr, "cudaSetDevice failed!  Do you have a CUDA-capable GPU installed?");
		goto Error;
	}

И это типичный севый бойлерплейт лет уже 30 как. Что там было про проверки?
Поэтому — где си/кресты — а где научные вычисления.

Молекулярная динамика. Самые ходовые пакеты:
LAMMPS — C++
GROMACS — C/C++
DLPOLY — Fortran

throw, try-except-finally

Если у вас такое случилось в научном коде, значит где-то косяк во входных данных или алгоритме. И нужно искать причину, почему так случилось, а если вы проглотили ошибку и продолжили считать…

Молекулярная динамика. Самые ходовые пакеты:
LAMMPS — C++
GROMACS — C/C++
DLPOLY — Fortran

Ну так и сидите тогда на медленных CPU ) а если вам таки ускорение занедорого — извольте написать CUDA-ядрышки и покормить видюху данными.
И вот тут — внезапно — оказывается, что:
а) задача разбивается на 3 независимых: управление жизненным циклом контекста, организация пайплайна «источник — ядро — получатель», и собственно — перенос вычислительного ядра на синтаксис CUDA.
б) жизненный цикл вообще пофиг на чем делать — были бы байндинги к CUDA, и он туп и единообразен для любого CUDA-приложения
в) в случае, когда исходные данные лежат в большом удаленном источнике (БД, координатор распределенных вычислений) — бутылочным горлышком оказывается не локальное I/O, не процессор, не видеокарта — а сеть.

То есть 2/3 такого ускоренного приложения — абсолютно пофиг на чем писать, и языки с неявным управлением памятью и исключениями выигрывают: бойлерплейта меньше, код выразительнее, время до запуска прототипа катастрофически сокращается.
А 1/3 решает предметник, который хорошо раскурил особенности зеленых карточек и сумел оптимизировать ядро.
Если у вас такое случилось в научном коде, значит где-то косяк во входных данных или алгоритме. И нужно искать причину, почему так случилось, а если вы проглотили ошибку и продолжили считать…

С какого перепугу в входных данных? С какого перепугу в алгоритме? Косяк может случиться в I/O и он может быть исправлябельным (recoverable, retriable). Косяк может случиться где угодно, но когда количество мест, где косяки могут случаться переваливает за разумное количество — вложенные if-ы да еще круто приправленные goto-хами просто ломают понимание и читабельность кода.
Тогда как исключения предоставляют структурированный подход к обработке ошибок. И даже если у меня случилась ошибка во время расчета — это еще не повод программе подымать лапки и валиться в корку. Это повод промаркировать текущий workunit как сбойный, просигнализировать, и попросить другой.
а если вам таки ускорение занедорого — извольте написать CUDA-ядрышки и покормить видюху данными.

Так так и делают. Все эти пакеты имеют параллельную версию, по крайней мере для вычислительных кластеров.
Все эти пакеты имеют параллельную версию, по крайней мере для вычислительных кластеров

«параллельная версия» не то же самое, что «GPGPU-версия». сильно не то же самое.
LAMMPS точно имеет версию для GPU. Возможно и остальные
Они делают 1000 проверок там, где достаточно 10.

Например? С пруфами, желательно.

Ну, например, проверка выхода индекса за границы массива.

Вот этот Python код завершится исключением, так как проверяет границы:
arr = ['a', 'b', 'c']
print (arr[10])


Этот код на C++ код завершается нормально, так не делает такой проверки.
#include <iostream>

int main()
{
    char arr[] = {'a', 'b', 'c'};
    std::cout << arr[10];

    return 0;
}


Да, с одной стороны это возможность выстрелить себе в ногу. А с другой стороны, если у меня в программе невозможно выйти за пределы массива by design, зачем это каждый раз это проверять? В большинстве случаев это не мешает, но когда обрабатываются большие объемы данных, и важна производительность, это может сыграть роль. И это только один из примеров.

Кстати, в C++ тоже можно писать так, чтобы библиотека автоматически делала проверку. Следующий код на C++ завершится исключением:
#include <iostream>
#include <vector>

int main()
{
    std::vector<char> arr = {'a', 'b', 'c'};
    std::cout << arr.at(10);

    return 0;
}

То есть в C++ можно писать и так, и так. Но нужно время, обучить человека это понимать. И поэтому всё упирается в длительность подготовки специалиста.

Под нормальными исключениями я понимаю наличие конструкций throw, try-except-finally

В С++ исключения есть.
А с другой стороны, если у меня в программе невозможно выйти за пределы массива by design, зачем это каждый раз это проверять?

Потому что это правило хорошего дизайна программы.

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

Вот вы щас серьезно? if в машинном коде обычно разворачивается до jz/jnz и других условных переходов. там даже без спекулятивного исполнения разница несколько тактов обычно. И я специально выше обратил внимание, что в гибридной модели бутылочное горлышко у вас будет не процессор, а I/O. Так что — экономия на спичках.
Это слишком упрощенное понимание. Для развертывания в векторные инструкции, подобное — стоппер для компилятора.
а при чем здесь векторные инструкции, если мы про GPGPU?
Да потому что они там есть.
А с другой стороны, если у меня в программе невозможно выйти за пределы массива by design, зачем это каждый раз это проверять?

Потому что это правило хорошего дизайна программы.

Правило хорошего дизайна программы — делать ровно то, что необходимо для выполнения задачи.

Так что — экономия на спичках.

Неужели? Тогда объясните, пожалуйста, почему на банальном цикле и сложении, код на C++ оказывается в 100 раз быстрее кода на Python. И на этот раз попрошу пруфы от вас, если у вас есть контр-аргументы:

Тест:
$ ./test.py
100000000
Execution time: 45297.41560799994 milliseconds

$ g++ ./test.cpp && ./a.out
100000000
Execution time: 405 milliseconds


test.py
#!/bin/env python
import timeit

start = timeit.default_timer()

i_max = 100000000
i_print_div = i_max / 100

i = 0
while i < i_max:
    if i % i_print_div == 0:
        print(i, end='\r')
    i = i + 1

print(i)

stop = timeit.default_timer()
print("Execution time: {} milliseconds".format((stop - start) * 1000) )


test.cpp:
#include<iostream>
#include <chrono>

using namespace std;
using namespace std::chrono;

int main()
{

        auto start = high_resolution_clock::now();

        const unsigned long int i_max = 100000000;
        const unsigned long int i_print_div = i_max / 100;
        unsigned long int i;

        for (i = 0; i < i_max; i++)
                if (i % i_print_div == 0)
                        cout << i << "\r" << flush;

        cout << i << endl;

        auto stop = high_resolution_clock::now();
        auto duration = duration_cast<milliseconds>(stop - start);
        cout << "Execution time: " << duration.count() << " milliseconds" << endl;

        return 0;
}
Правило хорошего дизайна программы — делать ровно то, что необходимо для выполнения задачи.

Нене. Она еще должна работать предсказуемо, и не валить в случае сбоя за собой всю систему. Так что — проверять границы, null pointer-ы и ставить хуки чтобы взятую память отдавать обратно, если не хотите в автоматическое управление памятью.

Тогда объясните, пожалуйста, почему на банальном цикле и сложении, код на C++ оказывается в 100 раз быстрее кода на Python.

А почему вы апеллируете к элементарной арифметике, когда мы с вами обсуждаем прокачку данных на GPU, распределеночку, координацию через сеть? Нравится натягивать сов на глобус? Так возьмите асм, и ручками AVX512 в цикле разложите — еще порядок-два выиграете.

Вон, возьмите матрицу 4096х4096, заполните рандомно double-ами, и одной на CPU плюсами посчитайте скалярное произведение, а другую — CUDA-й через pyCUDA.
Или методом Монте-Карло посчитайте пи так и сяк до 10000-го знака.

Почему у меня Folding@Home на RTX 2080 воркюнит переваривает за час, а на Ryzen-е — не менее суток?
Разница в производительности, кстати, примерно та же, что и в вычислении скалярного произведения — примерно 20х.

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

… в тех сценариях, для которых она (программа) предназначена. И только в них. Если вы пишите тулу, которая запустится пару десятков раз, да еще и из ваших рук, прорабатывать все exceptional scenarions не имеет никакого смысла. Особенно если это негативно сказывается на скорости выполнения.

Вон, возьмите матрицу 4096х4096, заполните рандомно double-ами...

На этот раз хотелось бы увидеть код от вас. Сравнение реализации на Python и C++, замеры скорости.
прорабатывать все exceptional scenarions не имеет никакого смысла

Их в помянутом кейсе не более десятка точно.
Сорян, но программа, написанная без обработки исключений — это говнокод. Вы сейчас защищаете написание говнокода.
код
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

""" 
Multiplies two square matrices together using a *single* block of threads and 
global memory only. Each thread computes one element of the resulting matrix.
"""
import sys
import numpy as np
from pycuda import driver, compiler, gpuarray, tools
from datetime import datetime
import pycuda.autoinit

kernel_code_template = """
__global__ void MatrixMulKernel(float *a, float *b, float *c)
{
    // 2D Thread ID (assuming that only *one* block will be executed)
    int tx = threadIdx.x;
    int ty = threadIdx.y;

    // Pvalue is used to store the element of the matrix
    // that is computed by the thread
    float Pvalue = 0;

    // Each thread loads one row of M and one column of N, 
    //   to produce one element of P.
    for (int k = 0; k < %(MATRIX_SIZE)s; ++k) {
        float Aelement = a[ty * %(MATRIX_SIZE)s + k];
        float Belement = b[k * %(MATRIX_SIZE)s + tx];
        Pvalue += Aelement * Belement;
    }

    // Write the matrix to device memory;
    // each thread writes one element
    c[ty * %(MATRIX_SIZE)s + tx] = Pvalue;
}
"""

MATRIX_SIZE = int(sys.argv[1])

if __name__ == '__main__':
    a_cpu = np.random.randn(MATRIX_SIZE, MATRIX_SIZE).astype(np.float32)
    b_cpu = np.random.randn(MATRIX_SIZE, MATRIX_SIZE).astype(np.float32)
    c_start = datetime.now()
    c_cpu = np.dot(a_cpu, b_cpu)
    c_end = datetime.now()

    g_start = datetime.now()
    a_gpu = gpuarray.to_gpu(a_cpu) 
    b_gpu = gpuarray.to_gpu(b_cpu)
    c_gpu = gpuarray.empty((MATRIX_SIZE, MATRIX_SIZE), np.float32)

    kernel_code = kernel_code_template % { 'MATRIX_SIZE': MATRIX_SIZE  }
    mod = compiler.SourceModule(kernel_code)
    matrixmul = mod.get_function("MatrixMulKernel")
    if MATRIX_SIZE <= 32:
        bd = (MATRIX_SIZE, MATRIX_SIZE, 1)
    else:
        bd = (32, 32, 1)
    matrixmul(a_gpu, b_gpu,  c_gpu,  block = bd,)
    g_end = datetime.now()
    np.allclose(c_cpu, c_gpu.get())
    print('Matrix size   : ' + str(MATRIX_SIZE) + 'x' + str(MATRIX_SIZE))
    print('CPU Time taken: ' + str(c_end - c_start))
    print('GPU Time taken: ' + str(g_end - g_start))



а вот результаты. До 1024х1024 процессор выигрывает за счет того, что у CUDA накладные расходы выше:
./cudammul.py 1024
Matrix size   : 1024x1024
CPU Time taken: 0:00:00.020703
GPU Time taken: 0:00:00.101429

./cudammul.py 4096
Matrix size   : 4096x4096
CPU Time taken: 0:00:00.864960
GPU Time taken: 0:00:00.172028

./cudammul.py 16384
Matrix size   : 16384x16384
CPU Time taken: 0:00:47.109524
GPU Time taken: 0:00:01.600445

./cudammul.py 24576
Matrix size   : 24576x24576
CPU Time taken: 0:02:46.435724
GPU Time taken: 0:00:03.773536

Больше у меня на RTX2080 не влезает (11 Гб), а больше 65536х65536 не пролазит в питон даже на CPU — ошибка выделения памяти (требуется 16 Гб). Разбираться и тюнить (в наличии 48 Гб) лень.

Для работы на CPU используется NumPy, он написан на сях.

Одно дело, пишутся какие то тестовые программы, другое дело — реальные приложения. Попробуйте написать на своем питоне один из тестов NAS NPB, например BT или LU для газодинамики, и посмотрим, как быстро он будет выполняться на ГПУ, благо результатов очень много, в том числе мною оптимизированные версии под высокоуровневую модель DVMH, где не нужно писать ни строчки на языке C-CUDA. Думаю, что на питоне даже с такими библиотеками и близко не достичь нормальных результатов, а времени на разработку будет потрачено не мало.

Может ученым не нужно работать в Питоне как в Фортране и не бегать по массивам циклом for а работать с ними одним куском?


Если Питон собран с модулями под ATLAS или BLAS + LAPACK и последнее умеет на GPU то все вообще будет ок

В Фортране как раз цикл for не обязателен (другое дело, что оптимизирующий компиллятор может весьма эффективно такой цикл векторизовать и… даже распараллелить)

Более того, в фортране есть операции над массивами, которые заменяют циклы, например, А=В+С, где А, В, С — массивы.

Это и некоторые другие варианты я и имел в виду )
Поставил плюс.

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

Изобретателям ассемблера для GPU желаю отдельный котел в аду.
Прекрассный отрицательный пример того что CUDA это не панацея
и что ею надо знать как и уметь пользоваться :-)

для начала, CPU код можно еще оптимизиорвать если использовать векторные AVX инструкции
но и для GPU… можно не мерять время выделения памяти и копирования…
и тогда картина станет более многогранной и пестрой :-)
можно не мерять время выделения памяти и копирования…

Я посчитал, что рациональнее мерить время работы всей программы, а не конкретной части, чтобы определить какой из вариантов будет более медленным.
а если бы измерили ооочень бы удивились :-)

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

как я и сказал, померяв все по отдельности картина стала бы пестрой

В вашем примере есть еще и загруска самой тестовой апплицации
что бы нивилировать все накладные расходы
желательно повторять тест внутри тестовой аппликации несколько десятков раз
на одном и томже наборе данных без повторных выделений памяти и копирования данных

НЛО прилетело и опубликовало эту надпись здесь
Я правильно понимаю, что задача состоит в том, чтобы для чисел A и M найти такие числа x и y, что x^y = A, 2 <= y <= M?
Задача очень похожа на задачи из Project Euler и, наверно, поэтому не самая лучшая для сравнения CPU и GPU, поскольку, как мне кажется, она решается меньше, чем за секунду на CPU (в один поток) для любых 64-битных A и M. Идея в том, что если A — полный квадрат, то это легко проверить, просто взяв Math.Sqrt, а если A является степенью 3 или больше какого-то числа, то у него будет простой делитель, не превосходящий (примерно) один миллион, и его можно найти простым перебором. После факторизации числа A найти нужные x и y достаточно тривиально (у будет равен наименьшему общему делителю всех степеней простых чисел, входящих в разложение A).

Если абстрагироваться от задачи, то действительно интересно, почему CUDA медленней — обычно такое бывает либо потому, что время работы GPU мало по сравнению с временем подготовки и передачи данных, либо потому, что данные кладутся не в оптимальный для них тип GPU-памяти.
А где так учили блок-схемы рисовать? Или стандарты поменяли внезапно?
Меня немного удивляет, что никто не хочет указать автору Stalker31 на его ошибки. Попробую это сделать. (Если заметите ошибки и у меня, поправляйте, буду только «ЗА!»).

Про алгоритм. Вся его суть (без всякой математики по факторизации чисел) сводится к такой реализации:
void thread_task(size_t number, size_t max_degree, size_t tid, size_t* result)
{
   // tid - индекс потока
   size_t val = 1;
   for (size_t deg = 1; deg < max_degree; ++deg) {
      val *= tid;
      if (val == number)
         result[tid] = deg;
   }
}

Отмечу, что диапазон значений unsigned long long не обязательно хватит для любых входных данных, поэтому не забываем о возможном переполнении.

Но вернемся к Вашему методу.
Вот вы пишете ожидания завершения потоков:
thread *T = new thread[size];
Running_thread_counter = 0;
for (int i = 0; i < size; i++) {
   T[i] = thread(Upload_to_CPU, Number, Stepn, Stop, INPUT, max, i);
   T[i].detach();
}
while (Running_thread_counter < size - 1);//дождаться завершения выполнения всех потоков
// <= только не (size-1), а size

// <= А где у нас удаление ресурсов в виде delete[] T???


На самом деле, у Вас тут гонка данных (race condition) за общий разделяемый ресурс
Running_thread_counter
, поскольку каждый поток в Вашей реализации увеличивает эту переменную в самом начале своей работы (а нужно в конце, ибо мы ждем завершения!!!), и никто точно не знает, какое значение осталось в кэше потока. Для более корректного применения такого подхода нужно использовать
atomic<int> Running_thread_counter
, но в идеале я написал бы так:
for (int i = 0; i < size; i++) {
   T[i] = thread(Upload_to_CPU, Number, Stepn, Stop, INPUT, max, i);
}
for (int i = 0; i < size; i++) {
   T[i].join();   // <= ждем фактического завершения работы потока
}


Неужели Вас совершенно не удивило, что работа 1000 потоков на 8-ми ядерном процессоре быстрее, чем на графическом процессоре, у которого допустимое число потоков куда больше?

Реализация для графического процессора у Вас просто не подходящая, мягко говоря. Если писать на коленке, то в моем видении, это должно выглядеть примерно так:

__constant__ unsigned long long number, max_degree;
__shared__ unsigned long long buffer[1024];

__global__ void kernel(int* result)
{
   unsigned long long tid = threadId.x;
   unsigned long long val = 1;

   for (unsigned long long deg = 1; deg < max_deg; ++deg) {
      val *= tid;
      if (val == number) {
         buffer[tid] = deg;
         break;
      }
   }
   result[tid] = buffer[tid];
}

int main()
{
   ...
   // CUDA оперирует понятием варпа (cuda core в спецификации видеокарт, 32 потока), 
   // поэтому их число лучше делать кратным 32
   kernel<<<1, 1024>>>();  
   ...
}


Я бы Вам советовал почитать книжки:
  1. Энтони Уильямс. «Параллельное программирование на C++ в действии», «C++. Практика многопоточного программирования».
  2. Сандерс Джейсон. Технология CUDA в примерах. Введение в программирование графических процессоров

Ну и конечно, больше практики. Успехов!
Неужели Вас совершенно не удивило, что работа 1000 потоков на 8-ми ядерном процессоре быстрее, чем на графическом процессоре, у которого допустимое число потоков куда больше?

Я решил что у CPU выше производительность ядра.
Я решил что у CPU выше производительность ядра.

Все правильно. Одно ядро быстрее, чем один поток на GPU, раз этак в 10. Вот только ядер у CPU 8 (и то гипертрединг), а на видеокарте — больше тысячи. И если их все загрузить работой, то производительность видеокарты будет в десятки и сотни раз выше, чем CPU.
Проблема в том, что пересылка данных между хостом и видеокартой может занимать много-много времени :)

Используйте GPU по назначению, например для FFT2 и будет вам прирост производительности

Попытался воспроизвести сравнение на своей машине, но ни порядок величин, ни харатер зависимости не совпали ни для CPU ни для GPU.


Характаристики
  • CPU :Intel® Core(TM) i7-4790K CPU @ 4.00GHz (8 cores)
  • ОЗУ:32гбDDR4
  • GPU:GeForce GTX 1060 6GB
  • OS: Ubuntu 18.04 LTS
  • Cuda:
    Compute Capability 6.1
    Threads per Multiprocessor 1024
    CUDA 10.2

Тестовый скрипт
cat run.sh 
#!/bin/bash
dataset=("1679616\n500" "18143994\n2500" "82301184\n6500" "136048896\n8500" "123119982\n9500" "377913600\n14500")
echo "test GPU"
nvcc --version
nvcc test.cu
for data in ${dataset[*]}
do
    printf $data > input.txt
    cat input.txt
    echo ""
    time ./a.out
done

echo "test CPU"
g++ --version
g++ -O3 test.cpp -pthread
for data in ${dataset[*]}
do
    printf $data > input.txt
    cat input.txt
    echo ""
    time ./a.out
done

Сырой вывод скрипта
./run.sh 
test GPU
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2019 NVIDIA Corporation
Built on Wed_Oct_23_19:24:38_PDT_2019
Cuda compilation tools, release 10.2, V10.2.89
1679616
500
6^8=1679616
36^4=1679616

real    0m0.141s
user    0m0.037s
sys 0m0.093s
18143994
2500
no value

real    0m0.349s
user    0m0.206s
sys 0m0.138s
82301184
6500
no value

real    0m1.525s
user    0m1.122s
sys 0m0.401s
136048896
8500
108^4=136048896

real    0m2.557s
user    0m1.910s
sys 0m0.645s
123119982
9500
no value

real    0m3.227s
user    0m2.500s
sys 0m0.719s
377913600
14500
no value

real    0m7.147s
user    0m5.497s
sys 0m1.648s
test CPU
g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

1679616
500
6^8=1679616
36^4=1679616

real    0m0.053s
user    0m0.251s
sys 0m0.028s
18143994
2500
no value

real    0m0.806s
user    0m5.932s
sys 0m0.051s
82301184
6500
no value

real    0m5.214s
user    0m40.169s
sys 0m0.056s
136048896
8500
108^4=136048896

real    0m8.926s
user    1m8.601s
sys 0m0.052s
123119982
9500
no value

real    0m11.008s
user    1m25.921s
sys 0m0.136s
377913600
14500
no value

real    0m25.551s
user    3m20.153s
sys 0m0.160s

Результат (почему-то не сработал табличный markdown)


| Data            |     CPU |    GPU |
| --------------- | ------- | ------ |
| 1679616x500     |  0.053s | 0.141s |
| 18143994x2500   |  0.806s | 0.349s |
| 82301184x6500   |  5.214s | 1.525s |
| 136048896x8500  |  8.926s | 2.557s |
| 123119982x9500  | 11.008s | 3.227s |
| 377913600x14500 | 25.551s | 7.147s |

Сложно сказать, почему 1060 оказался в 10 раз быстрее 2060 и Intel в 4 раза быстрее AMD (но может быть конечно честные 8 ядер в 4 раза быстрее чем 4ядра*2потока).

Для начала скажу, что для тестирования я использовал приложение на C#, его код приведён в статье, поэтому результаты не совпадают.

А зачем писать с# приложение, нельзя напрямую запустить программу из консоли и вывести время её работы, как это было сделано чуть выше?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации