Exposer la non-détermination résultant du planificateur de threads du système d'exploitation


10

Comme nous le savons tous, les systèmes d'exploitation modernes ont des planificateurs de threads qui peuvent choisir différentes commandes pour planifier vos threads en fonction d'une logique interne dont votre code n'est pas au courant. Normalement, vous architecturez votre code multithread pour vous assurer que ce non-déterminisme qui vous est imposé n'affecte pas significativement votre sortie.

Le but ici est le contraire. Produisez un programme qui imprime les entiers dans l'intervalle [0,99] mais dans un ordre qui variera d'une exécution à l'autre en raison du planificateur de threads du système d'exploitation.

Vous devez parvenir à "suffisamment de non-déterminisme", défini comme:

En 10 séries séquentielles de 10 essais, votre programme doit produire au moins 9 permutations uniques dans chaque essai. Vous pouvez avoir un nombre raisonnable d'épreuves échouées de chaque côté des 10 consécutives qui réussissent.

Ou, pour le dire autrement, vous avez besoin de 100 exécutions de votre programme où chaque bloc de 10 exécutions a au plus deux exécutions qui produisent la même chose.

Ainsi, l'échange de temps en temps de 98 et de 99 ne suffira pas.

C'est un , donc la réponse qui utilise le moins d'octets l'emporte.

Menus détails

  • Écrivez votre sortie sur stdout, une entrée par ligne
  • Si vous modifiez le format en écrivant deux caractères entrelacés sur la sortie standard (même occasionnellement), ce qui entraîne des choses comme des nombres à trois chiffres ou des lignes vides, votre résultat n'est pas valide.
  • La seule exception à la règle ci-dessus est que vous pouvez émettre une seule ligne vide après avoir imprimé le dernier numéro requis (vous êtes les bienvenus)
  • Si vous manquez ou dupliquez des valeurs requises, votre résultat n'est pas valide
  • Votre programme n'a pas besoin d'être non déterministe sur un processeur monocœur (mais bravo s'il l'est)
  • Votre programme peut utiliser des threads / fibres verts qui ne sont pas réellement gérés par le noyau du système d'exploitation s'il ne répond toujours pas aux autres exigences du défi et que le système de thread fait partie de votre langue ou de la bibliothèque standard de votre langue
  • Le temps d'exécution de votre programme doit être fiable en moins de 5 secondes sur un processeur moderne
  • Vous ne pouvez pas spécifier les modifications de l'environnement qui se produisent en dehors de votre programme, telles que les attentes ou les modifications des paramètres; votre programme devrait réussir, qu'il soit exécuté 100 fois consécutivement ou avec une heure entre chaque exécution ou 100 secondes en parallèle (cela aiderait probablement en fait ...)
  • Vous pouvez utiliser un coprocesseur tel qu'un GPU ou Xeon Phi et son propre mécanisme de planification interne pour les tâches. Les règles s'appliquent à cela de la même manière qu'elles s'appliquent aux fils verts.
  • N'hésitez pas à provoquer le programmateur avec toutes sortes de sommeil, de rendements et d'autres astuces tant que vous obéissez aux règles spécifiées dans ce post

Opérations interdites

La seule source de non-déterminisme sur laquelle vous êtes autorisé à utiliser est lorsque le planificateur planifie l'exécution de vos threads. La liste suivante n'est pas exhaustive, destinée uniquement à fournir des exemples de choses que vous n'êtes pas autorisé à faire car elles admettent d'autres sources de non-déterminisme.

  • Accéder directement ou indirectement à tout type de capacité PRNG ou RNG matériel (sauf si elle fait partie intégrante du planificateur).
  • Lecture de tout type d'entrée (heure système, système de fichiers, réseau, etc.)
  • Lecture d'ID de thread ou d'ID de processus
  • Personnalisation du planificateur du système d'exploitation; vous devez utiliser un planificateur de système d'exploitation standard à partir d'un système d'exploitation traditionnel
  • La personnalisation de votre planificateur de thread / fibre verte est également interdite. Cela signifie que si vous écrivez une langue pour ce défi, vous devez utiliser des threads du système d'exploitation.

Validation des réponses

De préférence, une réponse fonctionnerait sur tous les systèmes d'exploitation courants et les processeurs modernes, avec des félicitations attribuées proportionnellement à l'étendue du support. Cependant, ce n'est pas une exigence du défi. Au minimum, une réponse doit prendre en charge un processeur SMP moderne et un système d'exploitation moderne. Je testerai les principales réponses dans la mesure de la disponibilité de mon matériel.

  • Si votre entrée ne produit pas la sortie requise sur un i7 5960x exécutant Windows 10 v1607 x64, spécifiez l'environnement requis
  • Si c'est quelque chose que je peux facilement reproduire avec VMWare Workstation, fournissez les spécifications exactes du système d'exploitation et de la machine virtuelle
  • S'il ne peut pas être produit dans l'une de ces conditions, enregistrez une capture d'écran simultanée du test comme décrit dans la section d'en-tête et un enregistrement vidéo portable de votre écran avec votre interaction avec la souris et le clavier (ou quel que soit le schéma de contrôle de votre calcul non standard utilisation de l'appareil) clairement visible et postez les deux vidéos avec votre réponse et expliquez pourquoi cela fonctionne
  • Alternativement, obtenez un utilisateur de longue date de bonne réputation (qui n'est pas vous) avec du matériel correspondant pour reproduire le résultat et se porter garant pour vous
  • Si votre entrée est dans un langage de programmation exotique qu'un développeur typique ne sera pas configuré pour compiler / jit / interpréter, fournissez des instructions de configuration
  • Si votre entrée dépend d'une version spécifique de l'interpréteur JVM / Python / autre, spécifiez laquelle
  • Si cela prend plus de 10 minutes consécutives pour obtenir vos 10 séries d'essais séquentielles réussies dans mes tests, vous échouez (alors ne laissez pas la condition de succès être un phénomène anormal, surtout si vous êtes près de la limite supérieure). lié à l'exécution)

4
-1 pour le "Si je m'ennuie ....". Je dirais préciser exactement combien de temps cela peut prendre.
Rɪᴋᴇʀ

@EasterlyIrk Il dit également "de manière fiable en moins de cinq secondes sur un processeur moderne"
Pavel

@ Pavel, ce n'est pas ce à quoi je fais référence. Les 10 essais réussis ne sont pas liés aux 5 secondes.
Rɪᴋᴇʀ

@EasterlyIrk Assez juste, c'est maintenant 10 minutes.
Techrocket9

@ Techrocket9 cool, downvote annulé.
Rɪᴋᴇʀ

Réponses:


4

Perl 6 , 27 octets

await map {start .say},^100

Explication:

      map {          },^100  # Iterate over the range 0..99, and for each of number:
           start             # Send the following task to a thread pool of OS threads:
                 .say        # Print the number, followed by a newline.
await                        # Wait until all tasks have completed.

J'espère que cela satisfait la tâche. (Sinon, faites-le moi savoir).

Essai:

Le script shell que j'ai utilisé pour tester un non-déterminisme suffisant:

#!/usr/bin/bash
for i in {1..10}; do
    set=""
    for j in {1..10}; do
        set="${set}$(perl6 nondet.p6 | tr '\n' ',')\n"
    done
    permutations="$(echo -e "$set" | head -n -1 | sort | uniq | wc -l)"
    echo -n "$permutations "
done

Pour moi, cela produit:

10 10 10 10 10 10 10 10 10 10 

Instructions d'installation:

J'ai exécuté le test avec un Rakudo Perl 6 à jour sur Linux 64 bits, bien que je suppose que cela fonctionnera sur d'autres plates-formes.

La page de téléchargement de Rakudo contient des instructions de configuration. J'ai compilé le mien à partir de git comme ceci:

git clone git@github.com:rakudo/rakudo.git
cd rakudo
perl Configure.pl --gen-moar --make-install
export PATH="$(pwd)/install/bin/:$PATH"

Essayez-le en ligne:

Ou testez-le simplement en ligne, en utilisant ce lien Try It Online fourni par @ b2gills. J'ai vérifié quelques exécutions et obtenu un ordre différent à chaque fois, mais je n'ai pas eu la patience de l'exécuter 100 fois via cette interface en ligne.



Validé sur Windows 10 x64 sur un i7 5960x avec Rakudo Perl version 2016.11
Techrocket9

4

bash, 32 28 octets

for i in {0..99};{ echo $i&}

J'ai couru cela 100 fois et j'ai obtenu 100 résultats différents.

Edit: 4 octets enregistrés grâce à @DigitalTrauma.


Tu m'as battu. En fait, le mien est un peu plus court for i in {0..99};{ echo $i&}, mais vous avez posté en premier - vous pouvez le prendre :)
Digital Trauma

Voici un moyen de le tester dans TIO. Cela fait 10 exécutions du script, capturant la sortie de chaque exécution, les md5ing la sortie de chaque exécution. Nous pouvons voir que les md5 sont différents à chaque fois. Les md5 sont triés pour rendre apparents les doublons potentiels.
Digital Trauma

@DigitalTrauma Non documenté mais sympa!
Neil

1
Oui - il y a une astuce pour ça.
Digital Trauma

Fait intéressant, cela ne parvient pas à atteindre "suffisamment de non-déterminisme" lorsqu'il est exécuté dans bash-on-windows officiel de Microsoft sur un E5-2699 v4, mais cela fonctionne dans une machine virtuelle RHEL Workstation avec 4 cœurs sur la même machine pour qu'il passe.
Techrocket9

2

PowerShell , 54 46 44 39 octets

workflow p{foreach -p($i in 0..99){$i}}

Les workflows PowerShell ne sont pas pris en charge dans TIO, vous ne pouvez donc pas y essayer. Cela devrait bien fonctionner sur votre machine Windows 10 :)

Définit une fonction pqui affichera la liste des nombres lorsqu'elle sera invoquée.

Horaire

Une seule exécution fonctionne de manière fiable en environ 600 ms sur ma machine. Les 100 tests définis ci-dessous se terminent en moins de 2 minutes.

Essai

Voici le code complet pour le tester:

workflow p{foreach -p($i in 0..99){$i}}
#workflow p{foreach($i in 0..99){$i}}
# uncomment above to prove testing methodology does detect duplicates

1..10 | % {
    $set = $_
    Write-Host "Set $set of 10"
    1..10 | % -b {
        $runs = @()
    } -p {
        $run = $_
        Write-Host "-- Run $run of 10 in set $set"
        $runs += "$(p)"
    } -e {
        Write-Host "-- There were $(10-($runs|Get-Unique).Count) duplicate runs in set $set"
    }
}

Sortie sur ma machine:

Set 1 of 10
-- Run 1 of 10 in set 1
-- Run 2 of 10 in set 1
-- Run 3 of 10 in set 1
-- Run 4 of 10 in set 1
-- Run 5 of 10 in set 1
-- Run 6 of 10 in set 1
-- Run 7 of 10 in set 1
-- Run 8 of 10 in set 1
-- Run 9 of 10 in set 1
-- Run 10 of 10 in set 1
-- There were 0 duplicate runs in set 1
Set 2 of 10
-- Run 1 of 10 in set 2
-- Run 2 of 10 in set 2
-- Run 3 of 10 in set 2
-- Run 4 of 10 in set 2
-- Run 5 of 10 in set 2
-- Run 6 of 10 in set 2
-- Run 7 of 10 in set 2
-- Run 8 of 10 in set 2
-- Run 9 of 10 in set 2
-- Run 10 of 10 in set 2
-- There were 0 duplicate runs in set 2
Set 3 of 10
-- Run 1 of 10 in set 3
-- Run 2 of 10 in set 3
-- Run 3 of 10 in set 3
-- Run 4 of 10 in set 3
-- Run 5 of 10 in set 3
-- Run 6 of 10 in set 3
-- Run 7 of 10 in set 3
-- Run 8 of 10 in set 3
-- Run 9 of 10 in set 3
-- Run 10 of 10 in set 3
-- There were 0 duplicate runs in set 3
Set 4 of 10
-- Run 1 of 10 in set 4
-- Run 2 of 10 in set 4
-- Run 3 of 10 in set 4
-- Run 4 of 10 in set 4
-- Run 5 of 10 in set 4
-- Run 6 of 10 in set 4
-- Run 7 of 10 in set 4
-- Run 8 of 10 in set 4
-- Run 9 of 10 in set 4
-- Run 10 of 10 in set 4
-- There were 0 duplicate runs in set 4
Set 5 of 10
-- Run 1 of 10 in set 5
-- Run 2 of 10 in set 5
-- Run 3 of 10 in set 5
-- Run 4 of 10 in set 5
-- Run 5 of 10 in set 5
-- Run 6 of 10 in set 5
-- Run 7 of 10 in set 5
-- Run 8 of 10 in set 5
-- Run 9 of 10 in set 5
-- Run 10 of 10 in set 5
-- There were 0 duplicate runs in set 5
Set 6 of 10
-- Run 1 of 10 in set 6
-- Run 2 of 10 in set 6
-- Run 3 of 10 in set 6
-- Run 4 of 10 in set 6
-- Run 5 of 10 in set 6
-- Run 6 of 10 in set 6
-- Run 7 of 10 in set 6
-- Run 8 of 10 in set 6
-- Run 9 of 10 in set 6
-- Run 10 of 10 in set 6
-- There were 0 duplicate runs in set 6
Set 7 of 10
-- Run 1 of 10 in set 7
-- Run 2 of 10 in set 7
-- Run 3 of 10 in set 7
-- Run 4 of 10 in set 7
-- Run 5 of 10 in set 7
-- Run 6 of 10 in set 7
-- Run 7 of 10 in set 7
-- Run 8 of 10 in set 7
-- Run 9 of 10 in set 7
-- Run 10 of 10 in set 7
-- There were 0 duplicate runs in set 7
Set 8 of 10
-- Run 1 of 10 in set 8
-- Run 2 of 10 in set 8
-- Run 3 of 10 in set 8
-- Run 4 of 10 in set 8
-- Run 5 of 10 in set 8
-- Run 6 of 10 in set 8
-- Run 7 of 10 in set 8
-- Run 8 of 10 in set 8
-- Run 9 of 10 in set 8
-- Run 10 of 10 in set 8
-- There were 0 duplicate runs in set 8
Set 9 of 10
-- Run 1 of 10 in set 9
-- Run 2 of 10 in set 9
-- Run 3 of 10 in set 9
-- Run 4 of 10 in set 9
-- Run 5 of 10 in set 9
-- Run 6 of 10 in set 9
-- Run 7 of 10 in set 9
-- Run 8 of 10 in set 9
-- Run 9 of 10 in set 9
-- Run 10 of 10 in set 9
-- There were 0 duplicate runs in set 9
Set 10 of 10
-- Run 1 of 10 in set 10
-- Run 2 of 10 in set 10
-- Run 3 of 10 in set 10
-- Run 4 of 10 in set 10
-- Run 5 of 10 in set 10
-- Run 6 of 10 in set 10
-- Run 7 of 10 in set 10
-- Run 8 of 10 in set 10
-- Run 9 of 10 in set 10
-- Run 10 of 10 in set 10
-- There were 0 duplicate runs in set 10

Fait intéressant, cela prend 51 secondes par exécution sur ma boîte E5-2699 v4, mais seulement 0,7 seconde sur mon ordinateur portable i5-5200U. Il atteint le degré de non-déterminisme requis sur l'ordinateur portable tout en entrant sous les 5 secondes max, il passe donc. Apparemment, le planificateur de PowerShell ne fonctionne pas bien avec de nombreux cœurs et de courtes tâches.
Techrocket9

Et cela prend 58 secondes sur le i7 5960x
Techrocket9

Hm ... 74 secondes sur un ordinateur portable i5-6300U. C'est peut-être un problème avec Windows 10 ou PowerShell 5.1, car l'i5-5200U est la seule machine parmi celles testées à ne pas utiliser Win10 (il exécute 8.1).
Techrocket9

@ Techrocket9 bizarre, je testais sur Win10, PS 5.1. Dans ISE cependant.
briantist

2

GCC sur Linux, 47 octets

main(i){for(i=99;fork()?i--:!printf("%d\n",i););}

Cela m'a donné des résultats différents à peu près à chaque fois, après avoir été compilé avec gcc(sans indicateur) la version 4.9.2. Plus précisément, j'étais sur Debian 8.6 64 bits (version 3.16.31 du noyau).

Explication

Si le fork()retourne zéro (processus enfant), la valeur de iest imprimée et la condition de boucle est fausse, car printfrenverra une valeur supérieure à zéro. Dans le processus parent, la condition de boucle est juste i--.


Identique à la réponse bash. Déterministe sous Windows, mais passe sous Linux (dans ce cas Debian).
Techrocket9
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.