«Вы думаете лучшими специалистами в своем деле становятся талантливые и гениальные люди? Нет, быть лучшим — это возможность каждого нормального человека! И у тебя, Хаброжитель, эта возможность тоже есть!»
Видимо из контекста предполагается что среди хабражителей нет отличных специалистов. И даже талантливых людей…
Если вы верите поговорке, что терпение и труд, всё перетрут, попробуйте выдавить пасту из тюбика, а потом вдавить ее обратно (Ильф и Петров, если правильно помню)
10000, 10000, 100, 47 — это не входные параметры. Это константы в программе. Поэтому строго говоря оптимизатор имеет полное право сделать сверку выражений и достаточно умный оптимизатор в итоге будет иметь просто число 39.
Посмотрел, почему run2 получился немного медленнее чем run (хотя по смыслу это вроде то же самое).
После дизассемблирования beam файла оказалось, что loop1 и loop2 адресуются статически, типа
{move,{integer,10000},{x,0}}.
{move,{x,2},{y,0}}.
{call,3,{test,loop2,3}}. < == Вот оно
{gc_bif,'-',{f,0},1,[{y,0},{integer,1}],{x,2}}.
{move,{x,0},{x,1}}.
{move,{x,2},{x,0}}.
{call_last,2,{test,loop1,2},1}.
А в dotimes вызывается преданная ей функция, которая в свою очередь создает переменную-функцию.
Типа в dotimes:
{function,dotimes,3,14}.
{label,13}.
{func_info,{atom,test},{atom,dotimes},3}.
{label,14}.
{test,is_eq_exact,{f,15},[{x,0},{integer,0}]}.
{move,{x,2},{x,0}}.
return.
{label,15}.
{allocate_zero,2,3}.
{gc_bif,'-',{f,0},3,[{x,0},{integer,1}],{y,1}}.
{move,{x,2},{x,4}}.
{move,{x,1},{x,2}}.
{move,{x,4},{x,1}}.
{move,{x,2},{y,0}}.
{call_fun,2}. < == Вот вызов
{move,{y,0},{x,1}}.
{move,{x,0},{x,2}}.
{move,{y,1},{x,0}}.
{call_last,3,{test,dotimes,3},2}.
Другими словами, Ваш первый вариант при переходе с R12 на R13 ускорился почти на треть — с 28 сек до 19 сек. Однако…
А рекзультаты на функциях улучшились чуть-чуть с 7.7 и 8.1 до 7.1 и 8.0 сек…
Valgrind хорош, спору нет, но он не панацея. Нужно иметь полный code coverage при тестировании, чтобы его вывод имел смысл. Если ваши тесты не покрывают всего кода, (там к примеру обработки ошибок или редко встречающиеся условия), то valgrind будет просто не в курсе о том, что там может быть что-то с памятью.
То есть, как писал Дейкстра " Тестирование может доказать наличие ошибок, но никогда не докажет их отсутствие". Поэтому образно говоря в языках с встроенным управлением памяти надо постараться, чтобы слохопотать NullPointer, а в C надо очень стараться чтобы не схлопотать.
пузомерки (benchmarks) дают возможность представлять не только «кто шустрее», но «насколько шустрее».
К примеру у вас есть питоновская программа, которая реализует какую-то логику. Надо добавить числодробительный модуль. Можно написать на питоне, можно сделать вставку на C. Имеет смысл возиться с С? не имея представления какой может быть выигрыш — не ответишь.
задачи на арифметические выражения — вполне реальный класс задач. Навскидку — любая статобработка. Навскидку — raytracing… Автор вполне ясно объяснил свои намерения в статье, поэтому я считаю ваш упрёк не состоятелен.
PS про shootout уже несколько раз писали в обсуждении.
1) Скорее наоборот. С С++ мегаофигены пока не надо следить за памятью. Иначе рискуешь воткнуться в sigsegv.
2) ocaml и erlang очень разные языки, «окамли и эрланги» — непоятно по какому признаку вы их обьединили. По наличию gc? так в python и java он тоже есть. По «функциональности» языка? так ocaml позволяет писать не функционально и приведенный пример как раз написан императивно… Поясните пожалуйста вашу мысль.
loop1 :: Int -> Int -> Int
loop1 0 x = x
loop1 n x = loop1 (n-1) (loop2 10000 x n)
loop2 :: Int -> Int -> Int -> Int
loop2 0 r _ = r
loop2 j r i = loop2 (j-1) ((r + ( (i * j) `rem` 100)) `rem` 47) i
; Evaluation took:
; 10.62 seconds of real time
; 10.296644 seconds of user run time
; 0.032002 seconds of system run time
; 20,399,873,134 CPU cycles
; 0 page faults and
; 0 bytes consed.
;
39
Видимо из контекста предполагается что среди хабражителей нет отличных специалистов. И даже талантливых людей…
После дизассемблирования beam файла оказалось, что loop1 и loop2 адресуются статически, типа
{move,{integer,10000},{x,0}}.
{move,{x,2},{y,0}}.
{call,3,{test,loop2,3}}. < == Вот оно
{gc_bif,'-',{f,0},1,[{y,0},{integer,1}],{x,2}}.
{move,{x,0},{x,1}}.
{move,{x,2},{x,0}}.
{call_last,2,{test,loop1,2},1}.
А в dotimes вызывается преданная ей функция, которая в свою очередь создает переменную-функцию.
Типа в dotimes:
{function,dotimes,3,14}.
{label,13}.
{func_info,{atom,test},{atom,dotimes},3}.
{label,14}.
{test,is_eq_exact,{f,15},[{x,0},{integer,0}]}.
{move,{x,2},{x,0}}.
return.
{label,15}.
{allocate_zero,2,3}.
{gc_bif,'-',{f,0},3,[{x,0},{integer,1}],{y,1}}.
{move,{x,2},{x,4}}.
{move,{x,1},{x,2}}.
{move,{x,4},{x,1}}.
{move,{x,2},{y,0}}.
{call_fun,2}. < == Вот вызов
{move,{y,0},{x,1}}.
{move,{x,0},{x,2}}.
{move,{y,1},{x,0}}.
{call_last,3,{test,dotimes,3},2}.
А это функция которая вызывается:
{function,'-run2/0-fun-1-',2,21}.
…
{make_fun2,{test,'-run2/0-fun-0-',3},2,29791076,1}.
…
То есть простыми словами — решение с dotimes создает дополнительно 10000 переменных типа функция, которые потом собираются garbage коллектором.
Попробовал на R13B1 —
6> timer:tc(test,start,[]).
{19239320,39}
7> timer:tc(test,run,[]).
{7114840,39}
8> timer:tc(test,run2,[]).
{8048659,39}
На R12B5 — 2> timer:tc(test,start,[]).
{28226647,39}
3> timer:tc(test,run,[]).
{7789521,39}
4> timer:tc(test,run2,[]).
{8189215,39}
Другими словами, Ваш первый вариант при переходе с R12 на R13 ускорился почти на треть — с 28 сек до 19 сек. Однако…
А рекзультаты на функциях улучшились чуть-чуть с 7.7 и 8.1 до 7.1 и 8.0 сек…
То есть, как писал Дейкстра " Тестирование может доказать наличие ошибок, но никогда не докажет их отсутствие". Поэтому образно говоря в языках с встроенным управлением памяти надо постараться, чтобы слохопотать NullPointer, а в C надо очень стараться чтобы не схлопотать.
К примеру у вас есть питоновская программа, которая реализует какую-то логику. Надо добавить числодробительный модуль. Можно написать на питоне, можно сделать вставку на C. Имеет смысл возиться с С? не имея представления какой может быть выигрыш — не ответишь.
PS про shootout уже несколько раз писали в обсуждении.
2) ocaml и erlang очень разные языки, «окамли и эрланги» — непоятно по какому признаку вы их обьединили. По наличию gc? так в python и java он тоже есть. По «функциональности» языка? так ocaml позволяет писать не функционально и приведенный пример как раз написан императивно… Поясните пожалуйста вашу мысль.
module Main where
loop1 :: Int -> Int -> Int
loop1 0 x = x
loop1 n x = loop1 (n-1) (loop2 10000 x n)
loop2 :: Int -> Int -> Int -> Int
loop2 0 r _ = r
loop2 j r i = loop2 (j-1) ((r + ( (i * j) `rem` 100)) `rem` 47) i
main = do
putStr «Result „
print (loop1 10000 0)
$ghc -O test.hs -o tesths
$time ./tesths
Result 39
real 0m7.442s
user 0m7.316s
sys 0m0.028s
7 c хвостиком (примерно полвосьмого) секунд
(defun tst () (let (( r 0)) (dotimes (i 10000) (dotimes (j 10000) (setf r (mod (+ r (mod (* i j) 100)) 47) ) )) r))
* (compile-file «test.lisp»)
* (load «test»)
; Loading #p"/home/w/sandbox/test.x86f".
T
* (time (tst))
; Compiling LAMBDA NIL:
; Compiling Top-Level Form:
; Evaluation took:
; 10.62 seconds of real time
; 10.296644 seconds of user run time
; 0.032002 seconds of system run time
; 20,399,873,134 CPU cycles
; 0 page faults and
; 0 bytes consed.
;
39
То есть 10 сек
-module(test).
-export([start/0,run/0]).
start() ->
Seq = lists:seq(0, 10000),
lists:foldl(fun(I, R) -> lists:foldl(fun(J, R2) -> (R2 + (I * J) rem 100) rem 47 end, R, Seq) end, 0, Seq).
run() ->
loop1(10000,0).
loop1(0,X) -> X;
loop1(N,X) ->
X1 = loop2(10000,X,N),
loop1(N-1,X1).
loop2(0,X,_) ->X;
loop2(J,R2,I) ->
R = (R2 + (I * J) rem 100) rem 47,
loop2(J-1,R,I).
3> c(test,[native]).
{ok,test}
5> timer:tc(test,start,[]).
{28965824,39}
6> timer:tc(test,run,[]).
{7921692,39}
7>
То есть вашим вариантом на моей машине — 28 сек, переписаным — 8 сек.
let r = ref 0;;
let tstfunc x =
for i = 1 to x do
for j = 1 to x do
r := (!r + (i * j) mod 100) mod 47;
done;
done;;
tstfunc 10000;;
Printf.printf «Result %d\n» !r;;
и дальше
$ ocamlopt speedtest.ml -o sptest
$ time ./sptest
Result 39
real 0m6.364s
user 0m6.136s
sys 0m0.044s
6 секунд на amd 4000+
Кстати что это за гейзенберговское наблюдение? Если принцип неопределенности Гейзенберга то там «Гейзенберг» и «наблюдение» отдельные слова.