Question Comment créer des données pour les tests de performance Criterion?


j'utilise critère pour comparer mon code Haskell. Je fais des calculs lourds pour lesquels j'ai besoin de données aléatoires. J'ai écrit mon fichier de référence principal comme ceci:

main :: IO ()
main = newStdGen >>= defaultMain . benchmarks

benchmarks :: RandomGen g => g -> [Benchmark]
benchmarks gen =
   [
     bgroup "Group"
     [
       bench "MyFun" $ nf benchFun (dataFun gen)
     ]
   ]

Je conserve des benchmarks et des générateurs de données pour eux dans différents modules:

benchFun :: ([Double], [Double]) -> [Double]
benchFun (ls, sig) = fun ls sig

dataFun :: RandomGen g => g -> ([Double], [Double])
dataFun gen = (take 5 $ randoms gen, take 1024 $ randoms gen)

Cela fonctionne, mais j'ai deux préoccupations. Premièrement, le temps nécessaire pour générer des données aléatoires est-il inclus dans le benchmark? j'ai trouvé une question qui touche à ce sujet mais honnêtement, je ne peux pas l'appliquer à mon code. Pour vérifier si cela se produit, j'ai écrit une version alternative de mon générateur de données incluse dans IO monad. J'ai placé la liste de tests avec main, appelée le générateur, extrait le résultat avec <- et ensuite transmis à la fonction testée. Je n'ai vu aucune différence de performance.

Ma deuxième préoccupation est liée à la génération de données aléatoires. À l'heure actuelle, le générateur une fois créé n'est pas mis à jour, ce qui conduit à générer les mêmes données en une seule opération. Ce n'est pas un problème majeur, mais néanmoins, ce serait bien de le faire correctement. Existe-t-il un moyen pratique de générer différentes données aléatoires dans chaque fonction de données *? "Neat" signifie "sans que les fonctions de données acquièrent StdGen dans IO"?

EDIT: Comme noté dans le commentaire ci-dessous, je ne me soucie pas vraiment du caractère aléatoire des données. Ce qui est important pour moi, c'est que le temps nécessaire pour générer les données ne soit pas inclus dans le benchmark.


10
2017-10-15 13:05


origine


Réponses:


Cela fonctionne, mais j'ai deux préoccupations. Premièrement, le temps nécessaire pour générer des données aléatoires est-il inclus dans le benchmark?

Oui ça irait. Toute la génération aléatoire devrait se produire paresseusement.

Pour vérifier si cela se produit, j'ai écrit une version alternative de mon générateur de données incluse dans IO monad. J'ai placé la liste de tests avec main, appelée le générateur, extrait le résultat avec <- et ensuite transmis à la fonction testée. Je n'ai vu aucune différence de performance.

Ceci est prévu (si je comprends ce que vous voulez dire); les valeurs aléatoires de randoms gen ne vont pas être générés jusqu'à ce qu'ils soient nécessaires (c'est-à-dire dans votre boucle de référence).

Existe-t-il un moyen pratique de générer différentes données aléatoires dans chaque fonction de données *? "Neat" signifie "sans que les fonctions de données acquièrent StdGen dans IO"?

Vous devez soit être en IO ou créer un StdGen avec une graine entière que vous fournissez, avec mkStdGen.

Ré. votre principale question sur la manière dont vous devriez extraire les données pRNG de vos benchmarks, vous devriez être capable d’évaluer complètement l’entrée aléatoire avant votre defaultMain (benchmarks g) des trucs avec evaluate et force comme:

import Control.DeepSeq(force)
import Control.Exception(evaluate)
myBench g = do randInputEvaled <- evaluate $ force $ dataFun g
               defaultMain [
                    bench "MyFun" $ nf benchFun randInputEvaled
                    ...

force évalue son argument à la forme normale, mais cela se produira encore paresseusement. Donc, pour le faire évaluer en dehors de bench nous utilisons evaluate pour tirer parti du séquençage monadique. Vous pouvez aussi faire des choses comme appeler seq sur la queue de chacune des listes de votre tuple, etc. si vous vouliez éviter les importations.

Ce genre de chose devrait fonctionner correctement, à moins que vous ayez besoin de stocker une grande quantité de données de test en mémoire.

MODIFIER: cette méthode est également une bonne idée si vous souhaitez récupérer vos données depuis IO, comme la lecture du disque, et que vous ne voulez pas que cela soit mélangé à vos benchmarks.


6
2017-10-15 13:37



Vous pouvez essayer de lire les données aléatoires à partir d'un fichier disque à la place. (En fait, si vous utilisez un système d'exploitation de type Unix, vous pouvez même utiliser /dev/urandom.)

Toutefois, en fonction de la quantité de données dont vous avez besoin, le temps d’E / S peut dépasser le temps de calcul. Cela dépend de la quantité de données aléatoires dont vous avez besoin.

(Par exemple, si votre benchmark lit des nombres aléatoires et calcule leur somme, il sera limité par les E / S. Si votre benchmark lit un nombre aléatoire et effectue des calculs importants basés uniquement sur ce chiffre, les E / S ajoutent à peine frais généraux du tout.)


0
2017-10-15 19:11