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

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

Мне кажется, что если взять максимальное число >int64, то итоги могут быть слегка другие.
Да. Для таких чисел уже другой подход.

А можно, пожалуйста, добавить для версии PHP флаги с которыми он запускался? Конкретнее: Флаги jit, кли-опкеша и jit буфера?


P.S. Сам проверил этот код (windows, i7 6700k), получилось соотношение примерно такое же, как приведённое, так что думаю, что у вас настройки этих флагов были схожие:


// PHP 8.0: ~4 секунды с копейками
php bench.php -dopcache.enable_cli=1 -dopcache.jit_buffer_size=10M -dopcache.jit=1255

// PHP 7.4: ~26 секунд с копейками
php bench.php -dopcache.enable_cli=1

P.P.S. Для сравнения


$ node -v
v14.5.0
$ node test.js
2.454
Да, конечно:
/usr/local/Cellar/php/8.0.0/bin/php -dopcache.enable=1 -dopcache.enable_cli=1 -dopcache.jit=1255 -dopcache.jit_buffer_size=100M ./php/index.php

У меня от раза к разу время было «рядом». То есть 7-8 и 21-22 сек, соответственно PHP8 и PHP7.

У меня в случае с PHP 8.0 JIT пару раз до 10 секунд поднималось.


Ну и ещё, возможно, на Linux будет немного быстрее, т.к. для V8 Windows одна из основных платформ, а для PHP она поддерживается в режиме совместимости, т.к. серверов на винде не так что бы много очень...

Круто: 4 сек против 26!
А теперь включите оптимизацию в C++ компиляторе :)
Компилятор может догадаться что всё это абсолютно бесполезно, потому что функция чистая, а результат её вычисления сразу выкидывается.
Да, в теории компиляторы и интерпретаторы должны и могут оптимизировать. Особенно не используемые куски. Надо будет, это дело, прогнать через дизасемблер. Почему я сразу не подумал про него.
Просто без включения оптимизатора результаты не очень объективны, так как обычно он включен.
Не догадается, если, например, считать количество найденых праймов и в конце выводить результат на консоль.

Ну а в целом, слишком простой пример, думаю, тут просто негде разгуляться нормальному оптимизатору.

Приличный C/C++ компилятор к чертям выкинет весь цикл, так как isPrime функция без побочных эффектов и результат нигде не используется.


main:                                   # @main
        push    rbx
        call    clock
        mov     rbx, rax
        # nothing here
        call    clock
        sub     rax, rbx
        ...

(https://godbolt.org/z/4Wfcsj, clang 11.0.0 -O2)

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

Хотя в любом случае не вижу смысла в таких простых бенчмарках, где тестятся 2 вложенных цикла. Здесь нечего вообще оптимизировать, кроме как выкинуть вызов функции, а значит JIT компиляторы конечно будут выдавать результат на равне с C++.
А вот там где кода много, плюс всякие структуры и классы, которые оптимизатор может попробовать разобрать на части, в общем там, где GCC и CLang/LLVM делают десятки, если не сотни оптимизационных проходов по коду вылизывая его, там уже будет видно кто есть кто, ибо JIT компилятор обязан быть быстрым по-определению и ему доступны только самые быстрые и очевидные оптимизации.
Машина, на которой производились запуски: MacBook Pro

Я пару лет назад подобный простой вычислительный питоновский (python3) тестик (с арифметическими действиями) прогонял на всех своих компах, включая imac. Так вот в макос у меня этот тестик почему то выполнялся в два раза дольше, чем на том же маке в линуксе, который работал в Parallels. Я обратил внимание на такую большую разницу, но в причинах не разбирался, т.к. в итоге вообще отказался от макос (не моё).
Думаю, тестирование лучше было бы проводить на серверном железе с каким-нибудь линуксом (например, дебиан) — там, где собственно эти языки по большей части актуальны (особенно PHP).

О, сравнение лошадей со стрекозами!
Автор, а у вас Undefined Behavior в коде на C++ :)

Есть подозрение, что такими «оптимизациями» только clang страдает (могу быть не прав)
А, не заметил :) Я думал речь о сокращении заранее вычисляемых циклов (undefined в этом случае в тайминге)
У меня работало в обоих случаях. И время не изменилось.
Ну это наудачу. При отсутствии хитрых оптимизаций со стороны компилятора там будет неинициализированное значение со стека (вероятно ноль). Но в debug-build в MSVC будет что-то типа 0xCCCCCCCC, тогда цикл либо моментально пролетит без единой итерации, либо задумается всерьёз и надолго из-за отрицательного значения на старте.
Для питона ещё pypy хорош. Особенно для такого типа задач. На моей машине без него — 43.8, pypy3 — 4.9. Почти в 9 раз быстрее.
У меня на i7 4770MQ
0.011027 sec

Т.е. быстрее мака автора в 235 раз. Неплохо =).
Это с какими флагами компиляции C++? Без оптимизации на i9-9880H:
4.88964 sec

с -O2:
1.93054 sec

с -O3:
0.006242 sec
Это с -O3 на GCC 9.3.0.
Я это к тому, что настолько сильно влияет оптимизация и нет смысла сравнивать без оптимизации. Тем более с другими языками.
Где проект на Гитхабе итп, чтобы можно было пофиксить всё?
Не хватает ключей компиляции, скриптов сборки.

Для С++ минимум это -march=native -O2
Интересно, это js так вырвался или php медленный?
а где код на го?
Прошу прощения, сразу не добавил.

А почему шарп и джаву не добавили а тест? Было бы интересно.

В python замена `is_prime` на идиоматичный `all()` скорее всего изменит ситуацию:
all(not num % div for div in range(3, int(num**0.5)+1, 2))
Если все остатки нулевые, то что?
У себя на python3.6 (linux mint) проверил замену на all():
34 сек — с for
5.2 сек — с all()
в моем коде ошибка, там не должно быть not. Если исправить, время практически одинаковое.
Да там всё одинаковое )

3*3*3*5*5*7*11*13*17*19*23*29*31*37*41*43*47 == 0xbfffdd7cc41833d5
— это максимальное число, умещающееся в uint64 и делящееся без остатка на _все_ нечетные от 3 до 47 включительно. Таким образом твой ошибочный код прервётся, выполнив (49-3)//2 == 23 взятий остатков — это максимум, любое другое uint64 даст меньше.

Ближайшее большое простое — 0xbfffdd7cc4183473, _правильный_ код проверки его на простоту потребует int(0xbfffdd7cc4183473**.5)//2-1 == 1859772841 взятий остатков, т.е. в 80_859_688 раз больше.

Делаем выводы, расходимся )
НЛО прилетело и опубликовало эту надпись здесь
Сам по себе тест интересен, но совсем далёк от жизни — показывает «сферическую производительность в вакууме», т.к. теперь даже ARM поголовно мультиядерные и нет никакого смысла пускать вычислительные задачи в одном потоке.

Если надо что-то считать, то считать надо с максимальным использованием ресурсов.
Сможете переделать тест на загрузку 4+ ядер на какой-либо чуть более серверной ОС (Debian, CentOS/RHEL, Ubuntu наконец)?

Будет как минимум два сценария с существенным различием в цифрах:
1. Заранее распределяем «какой поток какой набор цифр считает» и запускаем их одновременно — соотношение будет максимально близко к вашим результатам
2. Цифры для проверки приходят снаружи приложения (или из отдельного потока) и динамические распределяются между worker потоками — тут начнёт играть роль умение языка координироваться между потоками.
НЛО прилетело и опубликовало эту надпись здесь
Очень было бы интересно, сколько времени интерпретатор тратит собственно на «интерпретацию текста»
То есть прогнать в них пустой цикл с такой же итерацией
А собственно вычисление — дело конкретных библиотек. И тогда вопрос стоит уже об эффективности той или иной библиотеки на конкретной операции

Ну и как выше отметили — код
for (int i ; i < N; i++)

ставит результат под сомнение )))
слегка смущает это:
C++: div <= sqrt(double(num))
Go: div <= int(math.Sqrt(float64(num)))
Node.js: div <= Math.sqrt(num)
PHP: $i <= sqrt($num) + 1
Python: int(math.sqrt(num)) + 1
(в Питоне правая граница не включается в интервал)

Глядишь, на N = 10_000_000 что-то по времени и набежит дополнительно для РНР
Меня смутило другое — в js в условии окончания цикла каждый раз вычисляется корень числа num:
for (let div = 3; div <= Math.sqrt(num); div += 2) {

Это оптимизатор js так хорошо работает или все же лучше вынести это вычисление из цикла наружу?
using BenchmarkTools

function isPrime(num::Int64)::Bool
    (num == 2) && return true;
    (num <= 1 || num % 2 == 0) && return false;

    @simd for i = 3:2:round(Int, sqrt(num))
        (num % i == 0) && return false;
    end
    return true;
end

function measure()
    for i = 1:10_000_000
        isPrime(i);
    end
end

@benchmark measure()

versioninfo()

Куда же без Julia… Код выше выдаст оценку времени в REPL.


Впрочем, есть оптимизированный алгоритм расчёта простых чисел, который почти в 3 быстрее. https://github.com/JuliaMath/Primes.jl/blob/master/src/Primes.jl#L144

romanitalian_net, там может понадобиться дважды нажать плэй. Переписал на AssemblyScript, который является подмножеством TypeScript, но который компилируется в WebAssembly. Хочу узнать время на той же железке что и результаты в статье.
Прожал. Вывело:
init = 0
3.165ms

А что с расходом памяти?

Хочу добавить до кучи результаты тестирования на фортране. Правда, у меня нет других языков, только С++ в VS2008. Поэтому смог сравнить только с ним. Итак, сначала я тупо перекопировал си-шный код, и сразу столкнулся с первой проблемой:
Я подозреваю, что тут что-то неладное: for (int i; i < N; i++) {

В фортране такой цикл не сделаешь даже с помощью Do While: еще при компиляции будет неинициализированная переменная. Ну да ладно. Написал i=0… а тест все равно не работает. Функция не вызывается, т.к. ее результат игнорится. Короче, пришлось добавить в цикл счетчик простых чисел, который потом идет на печать. Только после этого фортран заработал. Что меня удивило, он оказался даже чуть медленнее, чем С++: 9.0 сек против 7.1. Правда, тут может быть эффект ключей компиляции: в фортране у меня стоит отладочный режим, а в Си я все делал по умолчанию, так как язык не знаю совсем и сборку настраивать не умею :-((

Но в реальной жизни мне обычно нужны чуть более сложные вычисления, а не только целочисленные. Поэтому я добавил в цикл небольшой массивчик и парочку синусов, чтобы исключить всякий шанс на такую оптимизацию, при которой эти строки просто игнорятся (верхнюю границу цикла пришлось поправить, естественно):
// test_c.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <cmath>
#include <time.h>

using namespace std;


bool isPrime(int num)
{
    if (num == 2) {
        return true;
    }
    if (num <= 1 || num % 2 == 0) {
        return false;
    }

    double sqrt_num = sqrt(double(num));
    for (int div = 3; div <= sqrt_num; div +=2)
    {
        if (num % div == 0) {
            return false;
        }
    }
    return true;
}

int _tmain(int argc, _TCHAR* argv[])
{
    int N = 50000;	//000
    int counter = 0;
	float array1[50000];
	for (int ii = 0; ii < N; ii++) {
	   array1[ii]=(float)ii;
	}

    clock_t start, end;
    start = clock();
    float summa=0;
	
    for (int i = 0; i < N; i++) {
	  if (isPrime(i)) {
		counter++;
		array1[i]=array1[i]+(float)i;	// Противо-оптимизатор
		summa=0;
		for (int ii = 0; ii < N; ii++) {
		  summa +=sin(array1[ii])/ii;	// Сумма "элементов" массива
		}
		for (int ii = 0; ii < N; ii++) {
		   array1[ii]=array1[ii]+sin(summa)/ii;	// Еще чуть-чуть синусов 
		}
  	  }
    }
    end = clock();
	cout << (end - start) / ((double) CLOCKS_PER_SEC);
	cout << " sec \n";
	cout << counter;
	cout << " primes \n";
	cout << summa;
	cout << " \n";
    return 0;
}





А это фортран. Да, по сравнению с С он достаточно многословный (особенно описание переменных), зато код читать много проще и ошибки типа Undefined Behavior заведомо невозможны, если специально не искать для этого особые трюки. Я уж не говорю про автоматический контроль выхода индекса за границу массива и прочие мелкие радости, которые в моем фортране по умолчанию включены, так как на скорость не влияют особо...:
 
      logical function isPrime(num)
      integer*8 num, i8, sqrt_num 
c
      if (num <= 0) stop (-13)      ! Проверка входа
      isPrime=.true.;   if (num <= 3) return    ! Многие верят, что число 1 - простое
      isPrime=.false.;   
c
      if (mod(num,2) == 0) return       ! все четные числа >2 очень непростые...
c      
      sqrt_num = int(sqrt(real(num)))   ! Этот блок кода не проверял.
      do i8=3,sqrt_num,2                ! просто доверился автору теста,
        if (mod(num,i8) == 0) return    ! что если остаток от деления == 0,
      end do                            ! то число не простое.
c      
      isPrime=.true.;    return
      end

c
c.....ПРО ГРАММА:
      integer*8, parameter :: test_limit=50000     ! сначала было 10000000
      logical       isPrime
      integer*8     prime_counter, num, ii
      integer       i_hour1, i_min1, i_sec1, i_iisec1
      integer       i_hour2, i_min2, i_sec2, i_iisec2
      real*8 ::       array(test_limit)=[(i,i=1,test_limit)], tmp_array(test_limit)
      real*8 ::       time, summa
c
c     В цикле будем считать простые числа, иначе ОН оптимизирует цикл:
      call gettim(i_hour1,i_min1,i_sec1,i_iisec1) ! Да, у нас вот такой геморрой
      prime_counter=0       
      do num=1,test_limit
        if (isPrime(num)) then                  
          summa=0.                          ! Никуда ты у меня не денешься, 
          prime_counter=prime_counter+1     ! будешь по-честному вызывать isPrime ;-)
          array(num)=array(num)+num         ! И вот это тоже будешь считать
          forall(ii=1:test_limit) tmp_array(ii)=sin(array(ii))/ii
          summa=sum(tmp_array)          ! Массив меняется на каждом шаге!
          array=array+sin(summa)/num    ! И контрольный выстрел. Теперь 
        end if                          ! ты у меня хрен что оптимизируешь!!!
      end do
c      
      call gettim(i_hour2, i_min2, i_sec2, i_iisec2)
      time=(i_iisec2-i_iisec1)/100.+(i_sec2-i_sec1)+(i_min2-i_min1)/60.+(i_hour2-i_hour1)/3600.
c
c     Результат пошлем на консоль:
      write(6,'(i12,2(a,f5.2))') prime_counter,' штук; ',time,' сек;  ',summa
      end

Вот выдача сишной программы:
68.293 sec 
5133 primes 
-1.#IND 
А вот это — фортрановской:
5134 штук;  4.61 сек;   0.08

Разница во времени счета, хм… не очень понятная. Ведь согласно древним учениям, компилятор Си автоматически оптимизирует циклы?! Или нет?!? К сожалению, я не сумел найти правильные слова (т.е. библиотечные функции), чтобы как-то свернуть эти Си-шные циклы. Видимо, до уровня «зеленого чайника» в Си я пока не дотягиваю :-((
А без этих «правильных слов» фортран получается на порядок быстрее :-(((

Что же в сухом остатке? Если я новичок в языке, то выходит, что счетные задачи надо писать именно на фортране. Мало того, что сам язык много проще, так еще и библиотеки учить не нужно. Тупо взял простейшие операторы и получил результат. За который в других языках еще нужно бороться… В реальной науке задачи обычно все-таки больше похожи на мой тест, а не на первоначальный…
Ого, да фортран просто исключительный язык. Умудрился найти на одно простое число больше, чем C!!!
Да, я даже в комментах об этом написал:
isPrime=.true.;   if (num <= 3) return    ! Многие верят, что число 1 - простое
Дорогие коллеги по обсуждению! Можно ли здесь задать небольшой вопрос?
На Хабре я новичок, но вполне понимаю, что оценивая мое сообщение минусами, вы хотите как-то поправить мое поведение. И я совершенно не против исправиться, чтобы лучше соответствовать стилю сообщества. Но к сожалению, у меня не хватает интуиции, чтобы понять: что именно я сделал неправильно? Я ведь не могу исправить ошибку, если не знаю — в чем она заключается? Может ли кто-нибудь подсказать, хотя бы в ЛС?

Да, я не включал оптимизации в Си — но я честно написал, что просто не знаю, как это сделать. Все-таки, ради этого теста я первый раз в жизни создал проект на C в Студии. До этого вообще на С не писал — даже «Hello, world!» не было. Поэтому я даже не сомневался, что все необходимые оптимизации по умолчанию уже включены. Во всяком случае, в фортране после его установки (интеграции в Студию) было именно так. Посоветуйте, что именно надо включить в настройках проекта в VS 2008 — я выложу результаты теста с оптимизацией.
Или надо было указать версию ОС и т.д.? У меня ничего особенного, обычная Win 7-64. Подробности про процессор я не стал писать, т.к. у меня в другом окне идет счет на несколько суток, и на тестовые задачи выделяется только часть ресурсов системы. Поэтому для надежности я запускал каждый тест по несколько раз. Но т.к. основной процесс дает очень стабильную загрузку, то результаты отличаются в пределах 10%.
Компилятор С — встроенный из VS2008. Компилятор фортрана — Intel Visual Fortran XE 13. Да, он считается неплохим компилятором, но ему уже десять лет. Вряд ли другие хорошие современные компиляторы фортрана дадут разницу на порядок.
Что еще я не указал?
Я надеюсь, что здесь не минусуют просто за факты, которые кому-то не нравятся? Да, в одной конкретной ситуации фортран оказался на порядок быстрее Си. Но я-то тут при чем? Я всего лишь собрал и запустил четыре теста, два из которых близки к моим реальным задачам. Потом честно выложил исходные коды и их результаты.
Или здесь считается неприемлемым ироничный тон комментариев, и что я чуть-чуть отошел от ТЗ и учел 1, как простое? Ну да, я склонен следовать Олегу Янковскому, который советовал улыбаться, ибо все глупости на Земле делаются с серьезным выражением лица (Тот самый Мюнхгаузен).
Мы же не роботы…
Я надеюсь, что здесь не минусуют просто за факты, которые кому-то не нравятся?
Минусуют, даже за то что просто не понимают, о чем прочитали.

Но все же стоит просто потыкать и посмотреть опции компиляции. Он них не убудет. Да и в 2020 студия 2008 оказывается архаичной (фортран то вне времени) =)
фортран то вне времени

Как раз компилятор фортрана от интела не плохо умеет векторизировать и параллелить наивный код. Поэтому чем новее компилятор тем лучше он может использовать фичи нового железа. Но для программы из топика это умение не пригодится.

У меня оба компилятора достаточно старые, и тест совсем примитивный: синусы и массивы. А разница в скорости остается даже при включенной оптимизации в С++. На фортране 5.4-5.5с, а на С++ все равно 19-20с.
Какие еще настройки посоветуете проверить, кроме вкладки «Оптимизация»?
Я смотрел еще вкладку «Code Generation», но там список доступных настроек в проектах Си и Фортрана разный. Архитектура SSE2 в обоих случаях включена.

Никому не интересно какие результаты дает студия 2008…
Вы вместо теста простых чисел тестируете синусы. В таком случае лучше уже написать один простой тест для синусов вместо непонятной каши. Кроме того есть компилятор Си от Intel где реализация синусов будет скорее всего идентичной вашему одноименному компилятору Фортрана.

Никому не интересно какие результаты дает студия 2008…
Спасибо за прямоту, — теперь дошло, наконец ;-)
Жаль, что никто мне об этом три дня назад не сказал :-(
Наверно, мне бы и самому не понравилось, если бы кто-то затроллил обсуждение никому не интересным вопросом. Сейчас ругаюсь на себя, что не разобрался, как прятать лишнее в спойлер. Теперь мое первое сообщение мешает всем читать тему :-( А исправить уже никак :-((((
И про тест Вы правы, конечно. На фортране я так не пишу обычно. Меня сбила с толку попытка думать на Си, не зная про него почти ничего. Ок, больше не буду ;-)
Ну Вы ведь сами на свой вопрос отвечаете.
1) Сравнение оптимизированной сборки на фортране с неоптимизированной на C. Вот Вы серьезно не в состоянии поискать в сети как включить оптимизацию в проекте в вижуалке? Или Вы думаете, что это сравнимо по сложности с изучением языка и является серьезным поводом для новичка забросить изучение C и учить фортран?
2) Запуск бенчмарков проводился одновременно с выполнением каких-то вычислений, которые съедали большую часть аппаратных ресурсов — разница от запуска к запуску 10%. О каких объективных результатах вообще может идти речь?
3) Вы изменили тест чуть менее чем полностью. Добавили бесполезное жонглирование синусами, под предлогом того, что C выкидывал вызов isPrime при оптимизации, хотя для этого достаточно просто дописать перед ним
bool volatile result = isPrime(i);

что, опять же, не является какой-то секретной информацией, достаточно использовать интернет-поисковик. Такие действия как-бы намекают, что изначальный результат сравнения Вам не понравился, и Вы решили чуть подправить реализации, что бы он был поприятнее.
4) Ваши программы неэквивалентны. Причем не то что бы Вы чего-то недоглядели — нет, Вы добавили в комментарий неодинаковых выводы программ, и это Вас абсолютно не смутило.
Суммируя, Вы сравниваете две неэквивалентные программы с разными настройками оптимизации, написанные по в процессе подправленном ТЗ, и запускаемые в среде с дефицитом вычислительных ресурсов. И после этого в «ироничном тоне» делаете выводы о производительности языков в целом, которые, оказывается, не просто Ваши субъективные догадки, а прямо таки «факты, которые кому-то не нравятся». Думаю, Ваш комментарий минусят потому, что он — тотальная профанация.
Вот Вы серьезно не в состоянии поискать в сети как включить оптимизацию в проекте в вижуалке?
Я же говорю, что первый раз в жизни собираю программу на Си. Поэтому для меня эти ключи — темный лес. Все, что я умею в VS — это выключать оптимизацию при отладке фортран-программ (иначе дебагер не показывает значения переменных). Больше я в настройках проекта ничего не трогаю. Как говорится, работает — не трогай. Но скорость фортран-вычислений при этом меняется максимум на десятки процентов. Никак не в разы. Во-вторых, при «чистом» тесте (исходный вариант) скорость у фортрана и Си получилась примерно одинаковая. Поэтому я предположил, что на скорость таких простых расчетов оптимизация не влияет особо. В фортране именно так все. Именно поэтому возникла версия, что нужна не оптимизация, а вызов каких-то библиотечных функций для работы с массивами (которые в фортране = простой оператор).
И еще насчет свойств проекта: конечно, я туда лазил. Но там очень много настроек. Самая очевидная — это «включить оптимизацию» в верхней строке. Но при ее включении проект перестает компилироваться. Сейчас еще раз посмотрел текст сообщения:
"cl : Command line error D8016 : '/Ox' and '/RTC1' command-line options are incompatible 
Более глубоко я просто не рискнул залезать. Но раз это ключевой вопрос, то попробую сейчас и другие варианты этой опции. Если есть еще советы — тоже с удовольствием сделаю.

2) Запуск бенчмарков проводился одновременно с выполнением каких-то вычислений, которые съедали большую часть аппаратных ресурсов — разница от запуска к запуску 10%. О каких объективных результатах вообще может идти речь?
Ну вообще-то, разброс на 5% в Винде будет всегда, если в системе еще хоть что-то работает. Я же не промышленный тестировщик. Поэтому почтовая программа, файловый менеджер и т.п. активны всегда. Сейчас дополнительно висит «тяжелый» процесс, а разброс при десятке запусков не превышает 10%. Мне показалось, что это достаточная стабильность, чтобы результат представлял интерес.

3) Вы изменили тест чуть менее чем полностью.
Наверно, я не очень точно написал в своем сообщении. Сначала я запустил строго тот тест, который предложил топикастер. Программа на фортране была ПОЧТИ идентичной, с той разницей, что туда пришлось добавить счетчик простых чисел. Как я написал, в этом варианте программа на Си работала чуть быстрее.

Добавили бесполезное жонглирование синусами, под предлогом того, что C выкидывал вызов isPrime при оптимизации, хотя для этого достаточно просто дописать перед ним bool volatile result = isPrime(i);
Вообще-то я попытался написать, что все было не совсем так. Синусы я добавил вовсе не по этой причине, а потому, что хотел тестировать операции с плавающей точкой и массивами, которых в изначальном тесте не было. Но которые все время нужны мне на практике. Т.е. это фактически другой тест, дополнительный к первому. Наверно надо было это более четко сказать.

4) Ваши программы неэквивалентны. Причем не то что бы Вы чего-то недоглядели — нет, Вы добавили в комментарий неодинаковых выводы программ, и это Вас абсолютно не смутило.
Надеюсь, Вы не про число «1» спрашиваете? Это одна проверка при вызове функции — она на скорость не может заметно влиять.
Если же по существу (про синусы в цикле) — то я просто не знаю Си настолько хорошо, чтобы сделать полностью эквивалентную программу. Я надеялся, что кто-то из присутствующих не поленится в этот код заглянуть и подскажет, что именно там надо поправить, чтобы программа стала точно эквивалентной фортрану. Но я старался, чтобы количество итераций цикла и объем вычислений внутри него было одинаковыми.

Суммируя, Вы сравниваете две неэквивалентные программы с разными настройками оптимизации, написанные по в процессе подправленном ТЗ, и запускаемые в среде с дефицитом вычислительных ресурсов.
Ну вот да. Если не считать того, что сначала я честно запустил эквивалентные программы. И только потом сделал дополнительный тест с измененным ТЗ.
Хотя, насчет дефицита ресурсов я бы поспорил. Дело даже не в том, что у меня не было выбора. Но в реальной жизни реальные программы почти всегда работают в условиях дефицита ресурсов. Поэтому тестирование на загруженном компе, наверно, ТОЖЕ имеет право на существование? Понятно, что разброс результатов при этом растет, но я занимаюсь статистической обработкой сигналов и прекрасно понимаю, что этот разброс надо контролировать и как это сделать. Если несколько запусков дают разницу в результатах менее 10%, то для меня это приемлемая надежность. Особенно когда сравниваемые величины отличаются на порядок и ясно, что причину различий надо искать в чем-то другом.

Итого:
1) Спасибо за разъяснения! Постараюсь учитывать в будущем.
2) Если есть советы по настройке оптимизации (режим «для чайника»=on)- немедленно сделаю и обновлю результат.
3) Если есть поправки к си-коду «теста с синусами» — тоже немедленно сделаю. Если вдруг для кого-то синтаксис фортрана не совсем очевиден, могу пояснить, что оператор
forall(ii=1:test_limit)
— это фактически цикл, только чуть более короткая запись. А оператор
array=array+sin(summa)/num
прибавляет некоторое значение к каждому элементу массива. Кстати, возможно, что именно здесь была одна из проблем, если при Си-вычислениях синус считался каждый раз заново (хотя он всегда одинаковый).
Вдогонку: еще раз спасибо за замечания!
Действительно, у меня есть ошибка в коде на Си: вместо
array1[ii]=array1[ii]+sin(summa)/ii;
должно быть
array1[ii]=array1[ii]+sin(summa)/i;
Впрочем, на скорость это не повлияло особо. Вместо 68с стало 61с. А нужно, чтобы было 4-5с. Что еще может быть?
Также проверил все доступные ключи оптимизации: не компилируется все равно. При оптимизации на скорость ошибка:
cl : Command line error D8016 : '/O2' and '/RTC1' command-line options are incompatible
То же самое и при оптимизации на размер кода:
cl : Command line error D8016 : '/O1' and '/RTC1' command-line options are incompatible
В custom-настройки, уж извините, без мудрого советчика не рискну сам залазить :-((
Спасибо!
У меня плоховато с английским — прочитать страницу по ссылке я умею, а вот гуглить на международном получается плохо. Да, действительно, после переключения в Release все ключи оптимизации работают! Можно выбрать и полную оптимизацию, и оптимизацию на скорость! Впрочем, при любом выборе си-шная программа все равно выполняется за 19-20 сек, а фортрановская — за 5.4-5.5с (фоновый процесс изменился, поэтому время счета сегодня немного другое).
Также для полноты выкладываю заново свои тесты с массивами и плавающей точкой (внимание, это НЕ оригинальный тест топикастера!):
Надеюсь, что теперь они идентичны
// test_c.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <cmath>
#include <time.h>

using namespace std;


bool isPrime(int num)
{
    if (num == 2) {
        return true;
    }
    if (num <= 1 || num % 2 == 0) {
        return false;
    }

    double sqrt_num = sqrt(double(num));
    for (int div = 3; div <= sqrt_num; div +=2)
    {
        if (num % div == 0) {
            return false;
        }
    }
    return true;
}

int _tmain(int argc, _TCHAR* argv[])
{
    int N = 50000;	//50000 000
    int counter = 0;
	float array1[50000];
	for (int ii = 0; ii < N; ii++) {
	   array1[ii]=(float)ii;
	}

    clock_t start, end;
    start = clock();
	float summa=0;
	
    for (int i = 0; i < N; i++) {
      if (isPrime(i)) {
		counter++;
		array1[i]=array1[i]+(float)i; // Противо-оптимизатор
		summa=0;
		for (int ii = 0; ii < N; ii++) {
		  summa +=sin(array1[ii])/ii; // Сумма "эл-тов" массива
		}
		for (int ii = 0; ii < N; ii++) {
		   array1[ii]=array1[ii]+sin(summa)/i;
		}
  	  }
    }
    end = clock();
	cout << (end - start) / ((double) CLOCKS_PER_SEC);
	cout << " sec \n";
	cout << counter;
	cout << " primes \n";
	cout << summa;
	cout << " \n";
    return 0;
}


Фортран:
      logical function isPrime(num)
      integer*8 num, i8, sqrt_num 
c     
      isPrime=.true.;   if (num == 2) return            ! Все как в Си
      isPrime=.false.;  if ((num <= 1).or.(mod(num,2) == 0))  return
c      
      sqrt_num = int(sqrt(real(num))) ! Этот блок кода не проверял.
      do i8=3,sqrt_num,2              ! просто доверился автору теста,
        if (mod(num,i8) == 0) return  ! что если остаток от деления == 0,
      end do                          ! то число не простое.
c      
      isPrime=.true.
      end

c
c.....ПРОГРАММА:
      integer*8, parameter :: test_limit=50 000  
      logical       isPrime
      integer*8     prime_counter, num, ii
      integer       i_hour1, i_min1, i_sec1, i_iisec1
      integer       i_hour2, i_min2, i_sec2, i_iisec2
      real*8 ::       array(test_limit)=[(i,i=1,test_limit)], tmp_array(test_limit)
      real*8 ::       time, summa
c
c     В цикле будем считать простые числа, иначе ОН оптимизирует цикл:
      call gettim(i_hour1,i_min1,i_sec1,i_iisec1) ! Да, у нас вот такой геморрой
      prime_counter=0       
      do num=1,test_limit
        if (isPrime(num)) then                  
          summa=0.                        ! Никуда ты у меня не денешься, 
          prime_counter=prime_counter+1   ! будешь по-честному вызывать isPrime ;-)
          array(num)=array(num)+num       ! И вот это тоже будешь считать
          forall(ii=1:test_limit) tmp_array(ii)=sin(array(ii))/ii
          summa=sum(tmp_array)        ! Массив меняется на каждом шаге!
          array=array+sin(summa)/num  ! И контрольный выстрел. Теперь 
        end if                        ! ты у меня хрен что оптимизируешь!
      end do
c      
      call gettim(i_hour2, i_min2, i_sec2, i_iisec2)
      time=(i_iisec2-i_iisec1)/100.+(i_sec2-i_sec1)+(i_min2-i_min1)*60.+(i_hour2-i_hour1)*3600.
c
c     Результат пошлем на консоль:
      write(6,'(i12,2(a,f5.2))') prime_counter,' штук; ',time,' сек;  ',summa
      end

Ура, я научился прятать часть сообщения в спойлер! ;-)))
Правда, строки кода при этом стали короче (меньше символов в строке)
и читать программу не очень удобно. С этим пока не разобрался :-((
А Вы не надейтесь, проверьте выводы программ и убедитесь в том, что они все еще разные.
В каком смысле разные? Конечно, время счета отличается. И форматирование вывода разное (я просто не умею форматировать вывод в Си). Если Вы про количество найденных простых чисел, то там 5133 в обоих случаях. Или речь о чем-то другом?
Значения суммы одинаковые в обоих программах?
К тому же, в программе на C Вы используете стандартную функцию sin. Во-первых, эта функция принимает на вход число типа double, а данные в нее передаются в типе float — то есть, в программе на C часто выполняются преобразования float <-> double, которые довольно недешевые. Во-вторых, эта функция сама по себе может быть реализована как угодно, возможно тут лучше воспользоваться специализированной библиотекой.
И вообще, Ваш пример написан довольно сумбурно, например, сделать счетчики циклов double-переменными напрашивается само собой.
Да, с суммой непонятно
Еще в самом первом листинге видно, что программа на Си вместо числа выдает какой-то странный код. Я предполагал, что там какая-то ошибка в операторе печати, но надеялся, что кто-нибудь подскажет. Но меня устраивало, что раз это число используется, то значит компилятор не может оптимизировать эти вычисления — а именно это и было основной целью
Но я же говорю, это моя первая программа на Си. Хотя программой, конечно, это сложно назвать ;-((
Так вот, я правильно понял, что вместо float мне надо везде написать double? Это я сделал. Но время счета на Си не изменилось почти. Теперь 18-19с вместо 19-20. Так что лишнее преобразование типов, наверно влияло, но главная проблема не в нем.
Похоже, дело именно в вычислении синуса? Раз уж Вы взяли надо мной шефство, можете еще подсказать: как именно вызывать специализированную библиотечную функцию вместо стандартного sin? Нужно добавить какой-то #include? И/или изменить имя функции? Или что?
опция -ffast-math для gcc. для VC не скажу — поискать быстрая математика.

суть — будет вызываться команда сопроцессора, без обработки крайних случаев по IEEE
опция -ffast-math для gcc. для VC не скажу — поискать быстрая математика.
Спасибо!!! Это именно то, что надо!!! Теперь Си-шная процедура (с исправленным делением на 0) работает 5с —
ускорение вчетверо!
Теперь буду курить аналогичные настройки в фортране — если можно одной опцией добиться такого эффекта, это же почти революция ;-) Только проверю сперва, что точность не пострадала.

Я предполагал, что там какая-то ошибка в операторе печати, но надеялся, что кто-нибудь подскажет.
Дебаггером воспользоваться религия не позволяет? Интервалы циклов проверьте.
Но я же говорю, это моя первая программа на Си. Хотя программой, конечно, это сложно назвать ;-((
А откуда тогда жалобы на минусы к комментариям? Какие программы — такие и оценки.
Похоже, дело именно в вычислении синуса?
Не знаю в чем дело, у меня программа на C выполняется менее полутора секунд (Ryzen 3600). Возможно Вы и платформу не переключили на x64 — на x86 у меня чуть больше 5с.
А откуда тогда жалобы на минусы к комментариям? Какие программы — такие и оценки. /blockquote>
Честно говоря, я не жаловался, а только пытался
понять причину
Минус — это ведь совершенно нормальный способ вежливо намекнуть человеку, правда? Поэтому никаких обид. Вопросы были связаны с тем, что я не мог эти намеки понять, хотя честно пытался. Теперь понял ;-) Благо, мне в соседней ветке дополнительно пояснили ;-)

Насчет платформы — тоже спасибо за совет, попробую! Правда, пока не смог найти, где это. Но 5с уже и так получилось: у меня нужная опция называется
Floating Point Model = Fast
Еще в самом первом листинге видно, что программа на Си вместо числа выдает какой-то странный код.

Первый результат в гугле


-1.#IND (a synonym for NaN)

Смотрим на код. Вы делите на ноль там где делится на ii в цикле.

Спасибо!!!
Понимаю, что неправ :-(
Конечно, циклы в Си начинаются с нуля, а не с единицы.
Ну и тупой же я… :-(((
Увы, нет привычки пользоваться гуглом: по фортрану мне хватает книжек и локальной справки, а если там чего-то нет, то и в гугле обычно ничего не находится… Нужно было в свое время английский учить :0(
Конечно, циклы в Си начинаются с нуля, а не с единицы.

Циклы начинаются с любого числа, с любым инкрементом. А вот индексы в массивах с нуля.

Такие редкие маньяки обществу важны. Поставил плюсиков (блин, за карму без публикации выше +4 нельзя, так что надо писать статью. Даешь файт с Джулией! Код выше)

Но начать надо с того, что топик автор не включил оптимизации в С. Совсем. :fail:

ох это же фортран 77… Всё таки не вымерли ещё мамонты на хабре!

Спасибо за иронию ;-))
Но фортран, скорее, все же 2003. В фортране-77 многих использованных операторов еще не было — таких, как forall, или инициализация переменных/массивов при объявлении. Да и арифметические массивные операции тоже, кажется, появились чуть позже…

Да я шучу, просто врезались в глаза строчки начинающиеся на "c ".

Если бы я знал, как подложить фоновое изображение под код фортран-программы, то взял бы в качестве фона перфокарту. Я наверное тут единственный, кто не только их видел, но и запускал ;-) Это вообще возможно? (В смысле, подложить фон?)
А вообще, про историю развития фортрана неплохо написано вот тут. Кто интересуется археологией — очень рекомендую взглянуть ;-)
Косметическая правка кода для python с numba позволяет использовать распаралеливание по потокам и ускорить код.
Добавил импорт prange, и флаг parallel=True.
from numba import njit, prange
......
@njit(parallel=True) 
def do(n):
    for i in prange(n):
        is_prime(i)


У меня на i3-8100 получается 1.416 с.
Понятно, что сама функция не ускорилась, просто пример как легко c использованием numba распараллелить задачу. Наверное, подобное можно и с другими примерами провернуть, но наверное они усложнились больше чем код с numba.
Кстати, если в примере numba заменить «pytonic» цикл for на while со счетчиком — будет ускорения в нессколько раз. У меня он отработал всего за 0.2 с! Для сравнения, код автора у меня занял 3.48 с.
    div=3
    while div<int(math.sqrt(num)) + 1:
        if not num % div:
            return False
        div+=2

Не так красиво, но ускорение того стоит.

А оно точно выдает такой же результат, как обычная версия? И мы, вроде, договорились без параллельности :)

Во втором примере паралельности не использую, только тип цикла поменял.
Конечно, сразу сделал проверку. Самая простая — посчитать количество найденных простых чисел.
Оно оказалось таким же — 664579 всего. Но при этом вариант с циклом while замедлился с 0.2 до 2.0 с. Однако это все равно быстрее оригинального кода с циклом, который выполняется с 3.4-3.5 с. Прирост не такой фантастической, но все же почти в 2 раза быстрее оригинального кода с циклом for. Видимо, фантастический прирост на порядки наблюдался из-за того что оптимизатор как то слишком упрощал код, так как результат функции не использовался. Но как только я начал подсчет чисел, он замедлился в 10 раз, так как результат функции начал использоваться.
Под катом код — можете сами проверить что не используется распаралеливание:
код
import os
import math
from time import perf_counter
import numpy as np
from numba import njit

@njit(fastmath=True) 
def is_prime(num):
    if num == 2:
        return True
    if num == 1 or not num % 2:
        return False
    for div in range(3, int(math.sqrt(num)) + 1, 2): 
        if not num % div:
            return False
    return True


@njit(fastmath=True) 
def is_prime_fast(num):
    if num == 2:
        return True
    if num == 1 or not num % 2:
        return False
    div=3
    while div<int(math.sqrt(num)) + 1:
        if not num % div:
            return False
        div+=2
    return True


@njit() 
def do(n):
    count=0
    for i in range(n):
        count+=int(is_prime(i))
    return count

@njit('i8(i8)') 
def do_fast(n):
    count=0
    for i in range(n):
        count+=int(is_prime_fast(i))
    return count

if __name__ == '__main__':
    N = 10_000_000
    st = perf_counter()
    print(do(N))
    end = perf_counter()
    print(end - st)
    st = perf_counter()
    res = do_fast(N)
    end = perf_counter()
    print(res)
    print(end - st)



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

Спасибо. Да, это важный момент.

Большое спасибо за наводку на Нумбу!
Долгое время пытался ускорить свой кривой код. Где-то получилось использовать страйды и перемножение матриц вместо простых итераций по пикселям, но код получается «write only». С горя уже начал думать переписать узкие места на C++.
Теперь добавил одну строчку jit(nopython=True, cache=True) и все просто летает!
time without JIT: 17.295716762542725
time with JIT: 1.3494205474853516

Срочно переписываю все на Нумбу. Хотя и переписывать-то почти ничего не надо, просто расставить декораторы.
Вам, Спасибо!)
Стало интересно, как истинному PHP-нику, увидеть этот результат на Kotlin.
Получилось вот так:
import kotlin.math.sqrt

fun main(args: Array<String>) {
    val startTime = System.currentTimeMillis()
    run(10000000)
    val totalTime = (System.currentTimeMillis() - startTime) / 1000
    /**
     * 10000000 - Время выполнения: 5 сек.
     * 20000000 - Время выполнения: 15 сек.
     * 30000000 - Время выполнения: 28 сек.
     */
    println("Время выполнения: $totalTime сек.")

}


fun isPrime(number: Int): Boolean {
    if (number == 2) {
        return true;
    }
    if (number == 1 || number % 2 == 0) {
        return false;
    }
    var to = (sqrt(number.toDouble()) + 1).toInt();

    var i: Int = 3;

    while (i <= to) {
        i += 2
        if (number % i == 0) {
            return false;
        }
    }

    return true;
}

fun run(number: Int) {
    var i: Int = 0
    while (i <= number) {
        i++
        isPrime(i)
    }

}

Код на Go не честный!

В примере на C++ у вас используется 32 битный int, а в Go int не является типом с фиксированной размерностью, а по сему на 64-х битном CPU он соответствует int64 и как следствие результат «хуже»!

Ниже корректный пример который выполняется на моем i7-8565U за 1.691030168s, что практически эквивалентно C++.

package main

import (
	"fmt"
	"math"
	"time"
)

func isPrime(num int32) bool {
	if num == 2 {
		return true
	}
	if num <= 1 || num%2 == 0 {
		return false
	}
	to := int32(math.Sqrt(float64(num)))
	for div := int32(3); div <= to; div += 2 {
		if num%div == 0 {
			return false
		}
	}
	return true
}

func main() {
	st := time.Now()
	for i := int32(0); i < 10_000_000; i++ {
		isPrime(i)
	}
	fmt.Printf("%+v\n", time.Since(st))
}
Да, всё верно. На моём: 2.365265757s
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории