Transitivitas Auto-Spesialisasi dalam GHC

392

Dari dokumen untuk GHC 7.6:

[Y] ou sering kali bahkan tidak membutuhkan pragma SPESIALISASI. Saat menyusun modul M, pengoptimal GHC (dengan -O) secara otomatis mempertimbangkan setiap fungsi tingkat-atas yang dideklarasikan dalam M, dan mengkhususkannya untuk jenis yang berbeda di mana ia disebut dalam M. Pengoptimal juga mempertimbangkan setiap fungsi kelebihan beban yang tidak dapat diimpor yang diimpor, dan mengkhususkannya untuk berbagai jenis yang disebut dalam M.

dan

Selain itu, diberikan pragma SPECIALIZE untuk fungsi f, GHC akan secara otomatis membuat spesialisasi untuk semua fungsi tipe-kelas-kelebihan yang disebut oleh f, jika mereka berada dalam modul yang sama dengan pragma SPECIALIZE, atau jika mereka tidak dapat DILINCARKAN; dan seterusnya, secara transitif.

Jadi GHC secara otomatis harus mengkhususkan beberapa / kebanyakan / semua (?) Fungsi yang ditandai INLINABLE tanpa pragma, dan jika saya menggunakan pragma eksplisit, spesialisasi adalah transitif. Pertanyaan saya adalah: apakah auto -specialization transitive?

Secara khusus, inilah contoh kecil:

Main.hs:

import Data.Vector.Unboxed as U
import Foo

main =
    let y = Bar $ Qux $ U.replicate 11221184 0 :: Foo (Qux Int)
        (Bar (Qux ans)) = iterate (plus y) y !! 100
    in putStr $ show $ foldl1' (*) ans

Foo.hs:

module Foo (Qux(..), Foo(..), plus) where

import Data.Vector.Unboxed as U

newtype Qux r = Qux (Vector r)
-- GHC inlines `plus` if I remove the bangs or the Baz constructor
data Foo t = Bar !t
           | Baz !t

instance (Num r, Unbox r) => Num (Qux r) where
    {-# INLINABLE (+) #-}
    (Qux x) + (Qux y) = Qux $ U.zipWith (+) x y

{-# INLINABLE plus #-}
plus :: (Num t) => (Foo t) -> (Foo t) -> (Foo t)
plus (Bar v1) (Bar v2) = Bar $ v1 + v2

GHC mengkhususkan panggilan untuk plus, tetapi tidak mengkhususkan diri (+)dalamQux Num contoh yang membunuh kinerja.

Namun, pragma eksplisit

{-# SPECIALIZE plus :: Foo (Qux Int) -> Foo (Qux Int) -> Foo (Qux Int) #-}

menghasilkan spesialisasi transitif seperti yang ditunjukkan dokumen, sehingga (+)terspesialisasi dan kodenya 30x lebih cepat (keduanya dikompilasi dengan -O2). Apakah ini perilaku yang diharapkan? Haruskah saya hanya berharap (+)untuk mengkhususkan secara transitif dengan pragma eksplisit?


MEMPERBARUI

Dokumen untuk 7.8.2 belum berubah, dan perilakunya sama, jadi pertanyaan ini masih relevan.

crockeea
sumber
33
Saya tidak tahu jawabannya tetapi sepertinya itu terkait dengan: ghc.haskell.org/trac/ghc/ticket/5928 Mungkin layak membuka tiket baru atau menambahkan informasi Anda di sana jika Anda berpikir kemungkinan terkait dengan 5928
jberryman
6
@jberryman Tampaknya ada dua perbedaan antara yang tiket dan pertanyaan saya: 1) Dalam tiket, setara dengan plusitu tidak ditandai sebagai INLINABLE dan 2) simonpj menunjukkan bahwa ada beberapa inlining terjadi dengan kode tiket, tapi inti dari contoh saya menunjukkan bahwa tidak ada fungsi yang digarisbawahi (khususnya, saya tidak bisa menyingkirkan Fookonstruktor kedua , jika tidak, GHC inline stuff).
crockeea
5
ah baiklah Apa yang terjadi ketika Anda mendefinisikan plus (Bar v1) = \(Bar v2)-> Bar $ v1 + v2, sehingga LHS diterapkan sepenuhnya di situs panggilan? Apakah itu masuk sebaris dan kemudian spesialisasi masuk?
jberryman
3
@jberryman Lucu Anda harus bertanya. Saya sudah menyusuri jalan dengan pertanyaan ini yang mengarah pada laporan trac ini . Saya awalnya memiliki panggilan untuk plusditerapkan sepenuhnya secara khusus karena tautan-tautan itu, tetapi pada kenyataannya saya mendapat spesialisasi yang kurang : panggilan ke plusjuga tidak terspesialisasi. Saya tidak punya penjelasan untuk itu, tetapi berniat meninggalkannya untuk pertanyaan lain, atau berharap itu akan diselesaikan dalam jawaban untuk yang ini.
crockeea
11
Dari ghc.haskell.org/trac/ghc/wiki/ReportABug : "Jika ragu, cukup laporkan bug Anda." Anda seharusnya tidak merasa sedih, terutama karena jumlah haskeller yang benar-benar berpengalaman di sini tidak tahu bagaimana menjawab pertanyaan Anda. Kasus uji seperti ini mungkin benar-benar berharga bagi para pengembang GHC. Pokoknya selamat mencoba! Memperbarui pertanyaan jika Anda mengajukan tiket
jberryman

Jawaban:

4

Jawaban pendek:

Poin-poin kunci dari pertanyaan ini, seperti yang saya pahami, adalah sebagai berikut:

  • "Apakah transitif spesialisasi-otomatis?"
  • Haruskah saya hanya berharap (+) mengkhususkan secara transitif dengan pragma eksplisit?
  • (tampaknya dimaksudkan) Apakah ini bug GHC? Apakah tidak konsisten dengan dokumentasi?

AFAIK, jawabannya adalah TIDAK, kebanyakan ya tetapi ada cara lain, dan Tidak.

Kode inlining dan ketik spesialisasi aplikasi adalah pertukaran antara kecepatan (waktu eksekusi) dan ukuran kode. Level default mendapat beberapa percepatan tanpa kembung kode. Memilih tingkat yang lebih lengkap diserahkan pada kebijaksanaan programmer melalui SPECIALISEpragma.

Penjelasan:

Pengoptimal juga mempertimbangkan setiap fungsi kelebihan impor yang TIDAK TERLENGKAP, dan mengkhususkannya untuk berbagai jenis yang disebut dalam M.

Misalkan fadalah fungsi yang tipenya mencakup variabel tipe yang adibatasi oleh kelas tipe C a. GHC secara default mengkhususkan diri fsehubungan dengan jenis aplikasi (menggantikan auntuk t) jika fdisebut dengan aplikasi ketik kode sumber (a) fungsi dalam modul yang sama, atau (b) jika fditandai INLINABLE, maka setiap modul lain yang impor f dari B. Dengan demikian, auto-spesialisasi tidak transitif, hanya menyentuh INLINABLEfungsi diimpor dan menyerukan dalam kode sumber dari A.

Dalam contoh Anda, jika Anda menulis ulang contoh Numsebagai berikut:

instance (Num r, Unbox r) => Num (Qux r) where
    (+) = quxAdd

quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) x y
  • quxAddtidak diimpor secara khusus oleh Main. Mainmengimpor kamus instan Num (Qux Int), dan kamus ini berisi quxAdddalam catatan untuk (+). Namun, meskipun kamus diimpor, konten yang digunakan dalam kamus tidak.
  • plustidak memanggil quxAdd, ia menggunakan fungsi yang disimpan untuk (+)catatan dalam kamus contoh Num t. Kamus ini disetel di situs panggilan (masuk Main) oleh kompiler.
Diego E. Alonso-Blas
sumber