Pertanyaan Selisih dalam kinerja kode akselerasi dikompilasi berlari dari ghci dan shell


Masalah

Halo, saya menggunakan percepatan pustaka untuk membuat aplikasi yang memungkinkan pengguna untuk memanggil fungsi yang memproses gambar secara interaktif, itulah mengapa saya mendasarkan pada dan memperluas ghci menggunakan api gcc.

Masalahnya adalah ketika menjalankan compiled executable dari shell, perhitungan dilakukan di bawah 100ms (sedikit kurang dari 80), sementara menjalankan kode yang dikompilasi yang sama dalam ghci membutuhkan lebih dari 100ms (rata-rata sedikit lebih dari 140) untuk menyelesaikannya.

Sumber daya

kode sampel + log eksekusi: https://gist.github.com/zgredzik/15a437c87d3d8d03b8fc

Deskripsi

Pertama-tama: tes dijalankan setelah kernel CUDA dikompilasi (kompilasi itu sendiri menambahkan tambahan 2 detik tetapi bukan itu masalahnya).

Saat menjalankan perintah yang dikompilasi dari shell, perhitungan dilakukan di bawah 10ms. (shell first run dan second shell run memiliki argumen berbeda yang dilewatkan untuk memastikan data tidak di-cache di mana saja).

Ketika mencoba menjalankan kode yang sama dari ghci dan mengutak-atik data input, perhitungan mengambil lebih dari 100ms. Saya mengerti bahwa kode yang diinterpretasikan lebih lambat daripada yang dikompilasi, tetapi saya memuat kode yang dikompilasi yang sama dalam sesi ghci dan memanggil pengikatan tingkat atas yang sama (packedFunction). Saya telah secara eksplisit mengetiknya untuk memastikan itu khusus (hasil yang sama seperti menggunakan pragma KHUSUS).

Namun perhitungannya memakan waktu kurang dari 10 md jika saya menjalankannya main fungsi dalam ghci (bahkan ketika mengubah data input dengan :set args antara panggilan berturut-turut).

Dikompilasi Main.hs dengan ghc -o main Main.hs -O2 -dynamic -threaded

Saya bertanya-tanya dari mana datangnya overhead. Adakah yang punya saran mengapa ini terjadi?


Versi sederhana dari contoh yang diposkan oleh remdezx :

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Data.Array.Accelerate as A
import Data.Array.Accelerate.CUDA as C
import Data.Time.Clock       (diffUTCTime, getCurrentTime)

main :: IO ()
main = do
    start <- getCurrentTime
    print $ C.run $ A.maximum $ A.map (+1) $ A.use (fromList (Z:.1000000) [1..1000000] :: Vector Double)
    end   <- getCurrentTime
    print $ diffUTCTime end start

Ketika saya mengkompilasi dan menjalankannya dibutuhkan 0,09s menyelesaikan.

$ ghc -O2 Main.hs -o main -threaded
[1 of 1] Compiling Main             ( Main.hs, Main.o )
Linking main ...
$ ./main
Array (Z) [1000001.0]
0.092906s

Tetapi ketika saya mengkompilasi dan menjalankannya dengan interpreter 0,25 detik

$ ghc -O2 Main.hs -c -dynamic
$ ghci Main
ghci> main
Array (Z) [1000001.0]
0.258224s

32
2017-12-18 07:51


asal


Jawaban:


Saya menyelidiki accelerate dan accelerate-cuda dan taruh kode debug untuk mengukur waktu di bawah ghci dan dalam versi yang dikompilasi dan dioptimalkan.

Hasilnya di bawah, Anda dapat melihat jejak stack dan waktu eksekusi.

ghci dijalankan

$ ghc -O2 -dynamic -c -threaded Main.hs && ghci 
GHCi, version 7.8.3: http://www.haskell.org/ghc/  :? for help
…
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Ok, modules loaded: Main.
Prelude Main> Loading package transformers-0.3.0.0 ... linking ... done.
…
Loading package array-0.5.0.0 ... linking ... done.
(...)
Loading package accelerate-cuda-0.15.0.0 ... linking ... done.
>>>>> run
>>>>> runAsyncIn.execute
>>>>>  runAsyncIn.seq ctx
<<<<<  runAsyncIn.seq ctx: 4.1609e-2 CPU  0.041493s TOTAL
>>>>>  runAsyncIn.seq a
<<<<<  runAsyncIn.seq a: 1.0e-6 CPU  0.000001s TOTAL
>>>>>  runAsyncIn.seq acc
>>>>>   convertAccWith True
<<<<<   convertAccWith: 0.0 CPU  0.000017s TOTAL
<<<<<  runAsyncIn.seq acc: 2.68e-4 CPU  0.000219s TOTAL
>>>>>  evalCUDA
>>>>>   push
<<<<<   push: 0.0 CPU  0.000002s TOTAL
>>>>>   evalStateT
>>>>>    runAsyncIn.compileAcc
>>>>>     compileOpenAcc
>>>>>      compileOpenAcc.traveuseAcc.Alet
>>>>>      compileOpenAcc.traveuseAcc.Use
>>>>>       compileOpenAcc.traveuseAcc.use3
>>>>>       compileOpenAcc.traveuseAcc.use1
<<<<<       compileOpenAcc.traveuseAcc.use1: 0.0 CPU  0.000001s TOTAL
>>>>>       compileOpenAcc.traveuseAcc.use2
>>>>>        compileOpenAcc.traveuseAcc.seq arr
<<<<<        compileOpenAcc.traveuseAcc.seq arr: 0.105716 CPU  0.105501s TOTAL
>>>>>        useArrayAsync
<<<<<        useArrayAsync: 1.234e-3 CPU  0.001505s TOTAL
<<<<<       compileOpenAcc.traveuseAcc.use2: 0.108012 CPU  0.108015s TOTAL
<<<<<       compileOpenAcc.traveuseAcc.use3: 0.108539 CPU  0.108663s TOTAL
<<<<<      compileOpenAcc.traveuseAcc.Use: 0.109375 CPU  0.109005s TOTAL
>>>>>      compileOpenAcc.traveuseAcc.Fold1
>>>>>      compileOpenAcc.traveuseAcc.Avar
<<<<<      compileOpenAcc.traveuseAcc.Avar: 0.0 CPU  0.000001s TOTAL
>>>>>      compileOpenAcc.traveuseAcc.Avar
<<<<<      compileOpenAcc.traveuseAcc.Avar: 0.0 CPU  0s TOTAL
>>>>>      compileOpenAcc.traveuseAcc.Avar
<<<<<      compileOpenAcc.traveuseAcc.Avar: 0.0 CPU  0.000001s TOTAL
>>>>>      compileOpenAcc.traveuseAcc.Avar
<<<<<      compileOpenAcc.traveuseAcc.Avar: 0.0 CPU  0s TOTAL
<<<<<      compileOpenAcc.traveuseAcc.Fold1: 2.059e-3 CPU  0.002384s TOTAL
<<<<<      compileOpenAcc.traveuseAcc.Alet: 0.111434 CPU  0.112034s TOTAL
<<<<<     compileOpenAcc: 0.11197 CPU  0.112615s TOTAL
<<<<<    runAsyncIn.compileAcc: 0.11197 CPU  0.112833s TOTAL
>>>>>    runAsyncIn.dumpStats
<<<<<    runAsyncIn.dumpStats: 2.0e-6 CPU  0.000001s TOTAL
>>>>>    runAsyncIn.executeAcc
>>>>>     executeAcc
<<<<<     executeAcc: 8.96e-4 CPU  0.00049s TOTAL
<<<<<    runAsyncIn.executeAcc: 9.36e-4 CPU  0.0007s TOTAL
>>>>>    runAsyncIn.collect
<<<<<    runAsyncIn.collect: 0.0 CPU  0.000027s TOTAL
<<<<<   evalStateT: 0.114156 CPU  0.115327s TOTAL
>>>>>   pop
<<<<<   pop: 0.0 CPU  0.000002s TOTAL
>>>>>   performGC
<<<<<   performGC: 5.7246e-2 CPU  0.057814s TOTAL
<<<<<  evalCUDA: 0.17295 CPU  0.173943s TOTAL
<<<<< runAsyncIn.execute: 0.215475 CPU  0.216563s TOTAL
<<<<< run: 0.215523 CPU  0.216771s TOTAL
Array (Z) [1000001.0]
0.217148s
Prelude Main> Leaving GHCi.

menjalankan kode yang dikompilasi

$ ghc -O2 -threaded Main.hs && ./Main
[1 of 1] Compiling Main             ( Main.hs, Main.o )
Linking Main ...
>>>>> run
>>>>> runAsyncIn.execute
>>>>>  runAsyncIn.seq ctx
<<<<<  runAsyncIn.seq ctx: 4.0639e-2 CPU  0.041498s TOTAL
>>>>>  runAsyncIn.seq a
<<<<<  runAsyncIn.seq a: 1.0e-6 CPU  0.000001s TOTAL
>>>>>  runAsyncIn.seq acc
>>>>>   convertAccWith True
<<<<<   convertAccWith: 1.2e-5 CPU  0.000005s TOTAL
<<<<<  runAsyncIn.seq acc: 1.15e-4 CPU  0.000061s TOTAL
>>>>>  evalCUDA
>>>>>   push
<<<<<   push: 2.0e-6 CPU  0.000002s TOTAL
>>>>>   evalStateT
>>>>>    runAsyncIn.compileAcc
>>>>>     compileOpenAcc
>>>>>      compileOpenAcc.traveuseAcc.Alet
>>>>>      compileOpenAcc.traveuseAcc.Use
>>>>>       compileOpenAcc.traveuseAcc.use3
>>>>>       compileOpenAcc.traveuseAcc.use1
<<<<<       compileOpenAcc.traveuseAcc.use1: 0.0 CPU  0.000001s TOTAL
>>>>>       compileOpenAcc.traveuseAcc.use2
>>>>>        compileOpenAcc.traveuseAcc.seq arr
<<<<<        compileOpenAcc.traveuseAcc.seq arr: 3.6651e-2 CPU  0.03712s TOTAL
>>>>>        useArrayAsync
<<<<<        useArrayAsync: 1.427e-3 CPU  0.001427s TOTAL
<<<<<       compileOpenAcc.traveuseAcc.use2: 3.8776e-2 CPU  0.039152s TOTAL
<<<<<       compileOpenAcc.traveuseAcc.use3: 3.8794e-2 CPU  0.039207s TOTAL
<<<<<      compileOpenAcc.traveuseAcc.Use: 3.8808e-2 CPU  0.03923s TOTAL
>>>>>      compileOpenAcc.traveuseAcc.Fold1
>>>>>      compileOpenAcc.traveuseAcc.Avar
<<<<<      compileOpenAcc.traveuseAcc.Avar: 2.0e-6 CPU  0.000001s TOTAL
>>>>>      compileOpenAcc.traveuseAcc.Avar
<<<<<      compileOpenAcc.traveuseAcc.Avar: 2.0e-6 CPU  0.000001s TOTAL
>>>>>      compileOpenAcc.traveuseAcc.Avar
<<<<<      compileOpenAcc.traveuseAcc.Avar: 0.0 CPU  0.000001s TOTAL
>>>>>      compileOpenAcc.traveuseAcc.Avar
<<<<<      compileOpenAcc.traveuseAcc.Avar: 0.0 CPU  0.000001s TOTAL
<<<<<      compileOpenAcc.traveuseAcc.Fold1: 1.342e-3 CPU  0.001284s TOTAL
<<<<<      compileOpenAcc.traveuseAcc.Alet: 4.0197e-2 CPU  0.040578s TOTAL
<<<<<     compileOpenAcc: 4.0248e-2 CPU  0.040895s TOTAL
<<<<<    runAsyncIn.compileAcc: 4.0834e-2 CPU  0.04103s TOTAL
>>>>>    runAsyncIn.dumpStats
<<<<<    runAsyncIn.dumpStats: 0.0 CPU  0s TOTAL
>>>>>    runAsyncIn.executeAcc
>>>>>     executeAcc
<<<<<     executeAcc: 2.87e-4 CPU  0.000403s TOTAL
<<<<<    runAsyncIn.executeAcc: 2.87e-4 CPU  0.000488s TOTAL
>>>>>    runAsyncIn.collect
<<<<<    runAsyncIn.collect: 9.2e-5 CPU  0.000049s TOTAL
<<<<<   evalStateT: 4.1213e-2 CPU  0.041739s TOTAL
>>>>>   pop
<<<<<   pop: 0.0 CPU  0.000002s TOTAL
>>>>>   performGC
<<<<<   performGC: 9.41e-4 CPU  0.000861s TOTAL
<<<<<  evalCUDA: 4.3308e-2 CPU  0.042893s TOTAL
<<<<< runAsyncIn.execute: 8.5154e-2 CPU  0.084815s TOTAL
<<<<< run: 8.5372e-2 CPU  0.085035s TOTAL
Array (Z) [1000001.0]
0.085169s

Seperti yang kita lihat ada dua masalah utama: evaluasi fromList (Z:.1000000) [1..1000000] :: Vector Double yang mengambil 69 mdtk tambahan di bawah ghci (106ms - 37ms), dan performGC panggilan yang mengambil 57 mdtk ekstra (58 ms - 1 ms). Keduanya menjumlahkan perbedaan antara eksekusi berdasarkan ghci dan dalam versi yang dikompilasi.

Saya kira, bahwa dalam program yang dikompilasi, RTS mengelola memori dengan cara yang berbeda daripada di ghci, sehingga alokasi dan gc bisa lebih cepat. Kami juga dapat menguji hanya bagian ini yang mengevaluasi kode di bawah ini (tidak memerlukan CUDA sama sekali):

import Data.Array.Accelerate.Array.Sugar
import Data.Time.Clock                   (diffUTCTime, getCurrentTime)
import System.Mem                        (performGC)


main :: IO ()
main = do
    measure $ seq (fromList (Z:.1000000) [1..1000000] :: Vector Double) $ return ()
    measure $ performGC

measure action = do
    start <- getCurrentTime
    action
    end   <- getCurrentTime
    print $ diffUTCTime end start

Hasil:

  • mengevaluasi vektor 0,121653 di bawah ghci dan 0,035162s di versi yang dikompilasi
  • performGC mengambil 0,044876s di bawah ghci dan 0,00031s dalam versi yang dikompilasi.

Ini bisa jadi pertanyaan lain, tapi mungkin ada yang tahu: Bisakah kita selaras entah bagaimana pengumpul sampah bekerja lebih cepat di bawah ghci?


2
2018-01-22 09:14