Python arrêter de multiples processus lorsque l'on retourne un résultat?
j'essaie d'écrire un simple nonce-finder de preuve de travail en python.
def proof_of_work(b, nBytes):
nonce = 0
# while the first nBytes of hash(b + nonce) are not 0
while sha256(b + uint2bytes(nonce))[:nBytes] != bytes(nBytes):
nonce = nonce + 1
return nonce
maintenant j'essaie de faire ce multiprocesseur, pour qu'il puisse utiliser tous les cœurs CPU et trouver le nonce plus rapidement. Mon idée est d'utiliser multiprocessing.Pool
et exécutez la fonction proof_of_work plusieurs fois, en passant deux params num_of_cpus_running
et this_cpu_id
comme ceci:
def proof_of_work(b, nBytes, num_of_cpus_running, this_cpu_id):
nonce = this_cpu_id
while sha256(b + uint2bytes(nonce))[:nBytes] != bytes(nBytes):
nonce = nonce + num_of_cpus_running
return nonce
donc, s'il y a 4 noyaux, chacun calculera des nonce comme ceci:
core 0: 0, 4, 8, 16, 32 ...
core 1: 1, 5, 9, 17, 33 ...
core 2: 2, 6, 10, 18, 34 ...
core 3: 3, 7, 15, 31, 38 ...
Donc, je dois réécrire proof_of_work
donc quand n'importe lequel des processus trouve une nonce, tout le monde arrête de chercher des nonce, en tenant compte du fait que la nonce trouvée doit être la plus basse valeur possible pour laquelle les octets requis sont 0. Si un CPU accélère pour une raison quelconque, et retourne un nonce valide plus élevé que le nonce valide le plus bas, alors la preuve de travail n'est pas valide.
la seule chose que je ne sais pas faire est la partie dans laquelle un processus a ne s'arrêtera que si le processus B a trouvé une nonce qui est inférieure à la nonce qui est être calculé en ce moment par le processus A. Si son plus élevé, a continue de calculer (juste au cas) jusqu'à ce qu'il arrive au nonce fourni par B.
j'espère m'être bien expliquée. En outre, s'il ya une mise en œuvre plus rapide de tout ce que j'ai écrit, je serais ravi d'entendre à ce sujet. Merci beaucoup!
3 réponses
Une option facile est d'utiliser des micro-lots et de vérifier si une réponse a été trouvée. Des lots trop petits entraînent des frais généraux à partir de travaux parallèles, trop grande taille provoque d'autres processus pour faire du travail supplémentaire alors qu'un processus a déjà trouvé une réponse. Chaque lot devrait prendre 1 à 10 secondes pour être efficace.
exemple de code:
from multiprocessing import Pool
from hashlib import sha256
from time import time
def find_solution(args):
salt, nBytes, nonce_range = args
target = '0' * nBytes
for nonce in xrange(nonce_range[0], nonce_range[1]):
result = sha256(salt + str(nonce)).hexdigest()
#print('%s %s vs %s' % (result, result[:nBytes], target)); sleep(0.1)
if result[:nBytes] == target:
return (nonce, result)
return None
def proof_of_work(salt, nBytes):
n_processes = 8
batch_size = int(2.5e5)
pool = Pool(n_processes)
nonce = 0
while True:
nonce_ranges = [
(nonce + i * batch_size, nonce + (i+1) * batch_size)
for i in range(n_processes)
]
params = [
(salt, nBytes, nonce_range) for nonce_range in nonce_ranges
]
# Single-process search:
#solutions = map(find_solution, params)
# Multi-process search:
solutions = pool.map(find_solution, params)
print('Searched %d to %d' % (nonce_ranges[0][0], nonce_ranges[-1][1]-1))
# Find non-None results
solutions = filter(None, solutions)
if solutions:
return solutions
nonce += n_processes * batch_size
if __name__ == '__main__':
start = time()
solutions = proof_of_work('abc', 6)
print('\n'.join('%d => %s' % s for s in solutions))
print('Solution found in %.3f seconds' % (time() - start))
Sortie (un ordinateur portable avec un Core i7):
Searched 0 to 1999999
Searched 2000000 to 3999999
Searched 4000000 to 5999999
Searched 6000000 to 7999999
Searched 8000000 to 9999999
Searched 10000000 to 11999999
Searched 12000000 to 13999999
Searched 14000000 to 15999999
Searched 16000000 to 17999999
Searched 18000000 to 19999999
Searched 20000000 to 21999999
Searched 22000000 to 23999999
Searched 24000000 to 25999999
Searched 26000000 to 27999999
Searched 28000000 to 29999999
Searched 30000000 to 31999999
Searched 32000000 to 33999999
Searched 34000000 to 35999999
Searched 36000000 to 37999999
37196346 => 000000f4c9aee9d427dc94316fd49192a07f1aeca52f6b7c3bb76be10c5adf4d
Solution found in 20.536 seconds
avec un seul noyau, il a fallu 76.468 secondes. De toute façon ce n'est pas de loin le plus efficace pour trouver une solution, mais il fonctionne. Par exemple, si l' salt
est longue alors le SHA-256
l'État pourrait être pré-calculé après que le sel ait été absorbé et continuer la recherche de force brute à partir de là. Aussi le tableau d'octets pourrait être plus efficace que l' hexdigest()
.
Une méthode générale pour le faire est:
- pensez aux paquets de travail, par exemple pour effectuer le calcul pour un, une gamme ne devrait pas être long, disons 0,1 seconde à une seconde
- demandez à un gestionnaire de distribuer les paquets de travail au travailleur
- après qu'un paquet de travail a été terminé, informez le gestionnaire du résultat et demandez un nouveau paquet de travail
- si le travail est fait et le résultat a été trouvé à accepter les résultats des travailleurs et de leur donner un signal que plus de travail doit être exécuté, - les travailleurs pouvez maintenant mettre fin à
de cette façon, vous n'avez pas à vérifier avec le gestionnaire chaque itération (ce qui ralentirait tout), ou faire des choses désagréables comme arrêter un thread en cours de session. Inutile de dire que le directeur doit être filé en sécurité.
Il s'adapte parfaitement à votre modèle, vous avez encore besoin des résultats des autres travailleurs, même si un résultat a été trouver.
notez que dans votre modèle, il se peut qu'un thread ne soit pas synchronisé avec les autres threads, en retard. Vous ne voulez pas faire un autre million de calculs une fois le résultat trouvé. Je le répète simplement dans la question parce que je pense que le modèle est erroné. Vous devriez corriger le modèle au lieu de corriger l'implémentation.
Vous pouvez utiliser le multitraitement.File.)( Avoir une file D'attente par CPU / processus. Lorsqu'un processus trouve un nonce, il le met sur la file d'attente des autres processus. D'autres processus vérifient leur file d'attente (non-blocking) à chaque itération de la boucle while et s'il y a quelque chose dessus, ils décident de continuer ou de se terminer en fonction de la valeur dans la file d'attente:
def proof_of_work(b, nBytes, num_of_cpus_running, this_cpu_id, qSelf, qOthers):
nonce = this_cpu_id
while sha256(b + uint2bytes(nonce))[:nBytes] != bytes(nBytes):
nonce = nonce + num_of_cpus_running
try:
otherNonce = qSelf.get(block=False)
if otherNonce < nonce:
return
except:
pass
for q in qOthers:
q.put(nonce)
return nonce
qOthers est une liste de files d'attente ( chaque file=multiprocessing.File() ) appartenant à d'autres processus.
Si vous décidez d'utiliser Files d'attente comme je l'ai suggéré, vous devriez être en mesure d'écrire une meilleure/plus agréable mise en œuvre de l'approche ci-dessus.