Почему Seq.iter в 2 раза быстрее, чем для цикла, если цель для x64?
Отказ: это микро-бенчмарк, пожалуйста, не комментируйте цитаты, такие как "преждевременная оптимизация - это зло", если вы чувствуете недовольство по поводу темы.
Примерами являются релиз, предназначенный для x64,.Net4.5 Visual Studio 2012 F # 3.0 и запуск в Windows 7 x64
После профилирования я сузил узкое место в одном из своих приложений, поэтому хочу поднять этот вопрос:
Наблюдение
Если в цикле for in
или Seq.iter
нет цикла, то ясно, что они имеют одинаковые скорости. (update2 vs update4)
Если внутри цикла for in
или Seq.iter
есть цикл, кажется, что Seq.iter
равно 2x, как быстрее, чем for in
. (обновление vs update3) странно? (если они будут работать в fsi, они будут похожи)
Если он предназначен для anycpu и запускается на x64, во времени нет разницы. Таким образом, вопрос становится следующим: Seq.iter(update3) увеличит скорость 2x, если цель равна x64
Время:
update: 00:00:11.4250483 // 2x as much as update3, why?
updatae2: 00:00:01.4447233
updatae3: 00:00:06.0863791
updatae4: 00:00:01.4939535
Исходный код:
open System.Diagnostics
open System
[<EntryPoint>]
let main argv =
let pool = seq {1 .. 1000000}
let ret = Array.zeroCreate 100
let update pool =
for x in pool do
for y in 1 .. 200 do
ret.[2] <- x + y
let update2 pool =
for x in pool do
//for y in 1 .. 100 do
ret.[2] <- x
let update3 pool =
pool
|> Seq.iter (fun x ->
for y in 1 .. 200 do
ret.[2] <- x + y)
let update4 pool =
pool
|> Seq.iter (fun x ->
//for y in 1 .. 100 do
ret.[2] <- x)
let test n =
let run = match n with
| 1 -> update
| 2 -> update2
| 3 -> update3
| 4 -> update4
for i in 1 .. 50 do
run pool
let sw = new Stopwatch()
sw.Start()
test(1)
sw.Stop()
Console.WriteLine(sw.Elapsed);
sw.Restart()
test(2)
sw.Stop()
Console.WriteLine(sw.Elapsed)
sw.Restart()
test(3)
sw.Stop()
Console.WriteLine(sw.Elapsed)
sw.Restart()
test(4)
sw.Stop()
Console.WriteLine(sw.Elapsed)
0 // return an integer exit code
Ответы
Ответ 1
Это не полный ответ, но надеюсь, что он поможет вам продвинуться дальше.
Я могу воспроизвести поведение, используя ту же конфигурацию. Вот более простой пример профилирования:
open System
let test1() =
let ret = Array.zeroCreate 100
let pool = {1 .. 1000000}
for x in pool do
for _ in 1..50 do
for y in 1..200 do
ret.[2] <- x + y
let test2() =
let ret = Array.zeroCreate 100
let pool = {1 .. 1000000}
Seq.iter (fun x ->
for _ in 1..50 do
for y in 1..200 do
ret.[2] <- x + y) pool
let time f =
let sw = new Diagnostics.Stopwatch()
sw.Start()
let result = f()
sw.Stop()
Console.WriteLine(sw.Elapsed)
result
[<EntryPoint>]
let main argv =
time test1
time test2
0
В этом примере Seq.iter
и for x in pool
выполняется один раз, но между test1
и test2
все еще существует разница во времени:
00:00:06.9264843
00:00:03.6834886
Их ИЛ очень похожи, поэтому оптимизация компилятора не является проблемой. Похоже, что джиттер x64 не может оптимизировать test1
, хотя он может сделать это с помощью test2
. Интересно, что если я рефакторинг вложен в циклы в test1
как функцию, оптимизация JIT снова завершится:
let body (ret: _ []) x =
for _ in 1..50 do
for y in 1..200 do
ret.[2] <- x + y
let test3() =
let ret = Array.zeroCreate 100
let pool = {1..1000000}
for x in pool do
body ret x
// 00:00:03.7012302
Когда я отключу оптимизацию JIT с помощью описанной здесь техники , время выполнения этих функций сопоставимо.
Почему x64-джиттер не работает в конкретном примере, я не знаю. Вы можете дизассемблировать оптимизированный jitted-код для сравнения инструкций ASM по строкам. Может быть, кто-то с хорошим знанием ASM может узнать их отличия.
Ответ 2
Когда я запускаю эксперимент на своей машине (используя F # 3.0 в VS 2012 в режиме Release), я не получаю время, которое вы описываете. Вы постоянно получаете одинаковые номера при повторном запуске?
Я пробовал это примерно 4 раза, и я всегда получаю числа, которые очень похожи. Версия с Seq.iter
имеет тенденцию быть немного быстрее, но это, вероятно, не является статистически значимым. Что-то вроде (используя Stopwatch
):
test(1) = 15321ms
test(2) = 5149ms
test(3) = 14290ms
test(4) = 4999ms
Я запускаю тест на ноутбуке с Intel Core2 Duo (2.26Ghz), используя 64-битную Windows 7.