FISCO-BCOS 2.0 utilise une structure graphique dans le traitement des transactions. Les développeurs ont conçu un exécuteur de transactions parallèles (PTE) basé sur le modèle de graphe acyclique dirigé (DAG). PTE peut vous aider à utiliser pleinement les avantages d'un processeur multicœur afin que les transactions du bloc puissent être exécutées en parallèle.
Préface
Cette recherche compare des systèmes de mise en œuvre similaires à Ethereum et analyse les difficultés et les possibilités de réaliser une exécution parallèle des transactions.
Il convient de noter que les chaînes analysées pour cette recherche sont basées sur le schéma de conception du modèle de compte, sans compter le schéma UTXO.
Objets de recherche
FISCO-BCOS, l'une des chaînes de blocs du consortium qui prend en charge l'exécution parallèle de la vérification des transactions dans les blocs.
Chaîne publique Khipu, implémentation scala du protocole Ethereum.
Chaîne publique Aptos, Move Virtual Machine.
Difficultés avec l'exécution parallèle
Jetons un coup d'œil au processus traditionnel d'exécution des transactions.
Le module d'exécution extrait chaque transaction du bloc et l'exécute séquentiellement.
Le dernier état mondial sera modifié pendant le processus d'exécution, puis l'état sera additionné après l'achèvement d'une transaction pour atteindre le dernier état mondial après l'achèvement du bloc.
L'exécution du bloc suivant dépend strictement de l'état du monde du bloc actuel/précédent, par conséquent, ce processus d'exécution séquentiel à un seul thread n'est pas très adapté à une exécution parallèle.
Ci-dessous, les principaux conflits dans les méthodes d'exécution parallèles Ethereum actuelles :
Conflit de compte : si deux threads traitent le solde ou d'autres attributs d'un compte d'adresse en même temps, comment s'assurer qu'il est cohérent avec le résultat du traitement séquentiel, c'est-à-dire si l'état du monde est une machine à états finie définie ?
Conflit de stockage de la même adresse : où les deux contrats ont modifié le stockage de la même variable globale.
Conflit d'appel entre contrats : si le contrat A est déployé en premier, le contrat B doit attendre que le déploiement du contrat A soit terminé pour appeler le contrat A. Cependant, lorsque les transactions sont parallèles, il n'y a pas de séquence de ce type, ce qui entraîne un conflit.
Schémas d'exécution parallèle
FISCO-BCOS
Abstrait
FISCO-BCOS 2.0 utilise une structure graphique dans le traitement des transactions. Les développeurs ont conçu un Parallel Transaction Executor (PTE) basé sur le modèle Directed Acyclic Graph (DAG).
PTE peut vous aider à utiliser pleinement les avantages d'un processeur multicœur afin que les transactions du bloc puissent être exécutées en parallèle dans la mesure du possible.
En même temps, il fournit une interface de programmation simple et conviviale pour l'utilisateur, de sorte que l'utilisateur n'a pas à se soucier des détails fastidieux de la mise en œuvre parallèle.
Les résultats expérimentaux du programme de test de référence montrent que par rapport au schéma d'exécution de transaction en série traditionnel, PTE fonctionnant sur un processeur à 4 cœurs dans des conditions idéales peut atteindre une amélioration des performances d'environ 200 % à 300 %, et l'amélioration de calcul est proportionnelle au nombre de noyaux.
Plus il y a de cœurs, meilleures sont les performances.
Régime général
Un graphe dirigé acyclique est souvent appelé graphe acyclique dirigé (DAG).
Dans un lot de transactions, les ressources mutuellement exclusives occupées par chaque transaction sont identifiées ; puis un DAG dépendant des transactions est construit en fonction de la séquence des transactions dans le bloc et de la relation d'occupation des ressources mutuellement exclusives.
Comme le montre la figure ci-dessous, toutes les transactions avec un degré entrant de 0 (aucune tâche de précommande dépendante) peuvent être exécutées en parallèle. Le DAG de transaction à droite peut être obtenu par tri topologique basé sur l'ordre de la liste de transactions d'origine à gauche.
Architecture modulaire
Les utilisateurs initient des transactions directement ou indirectement via le SDK.
La transaction est ensuite synchronisée entre les nœuds, et le nœud avec les mêmes droits d'empaquetage invoque le Sealer (TxPool) pour prendre un certain nombre de transactions de (txpool) et les emballer dans un bloc. Après cela, les blocs sont envoyés à l'unité Consensus en préparation du consensus inter-nœuds.
La validation des transactions est effectuée avant qu'un consensus ne soit atteint, et c'est là que PTE commence son processus de travail. Comme le montre le diagramme d'architecture, PTE lit d'abord les transactions dans le bloc dans l'ordre et les entre dans le constructeur DAG, qui construit un DAG de transaction contenant toutes les transactions en fonction des dépendances de chaque transaction. PTE réveille ensuite le pool de nœuds de calcul. Utilisez plusieurs threads pour exécuter le DAG de transaction en parallèle. Le Joiner suspend le thread principal jusqu'à ce que le DAG ait été exécuté par tous les threads du pool de travail. À ce stade, le Joiner calcule la racine d'état et la racine de réception en fonction de l'enregistrement de modification d'état de chaque transaction et renvoie le résultat à l'appelant au niveau supérieur.
Une fois le bloc vérifié, le bloc est téléchargé sur la chaîne. Après l'exécution d'une transaction, si l'état de chaque nœud est cohérent, un consensus est atteint et le bloc est ensuite écrit dans le stockage sous-jacent, qui est enregistré en permanence sur la blockchain.
Processus de construction du DAG de transaction
Prenez toutes les transactions dans le bloc du bloc compressé.
Initialisez une instance DAG avec le nombre de transactions comme nombre maximal de sommets.
Lire toutes les transactions dans l'ordre. Si une transaction peut être fusionnée, résolvez son champ de conflit et vérifiez si des transactions précédentes sont en conflit avec elle. Si c'est le cas, construisez une arête de dépendance entre les transactions correspondantes. Si la transaction n'est pas fusionnable, on considère qu'elle doit être exécutée après que toutes les transactions précédentes ont été exécutées, donc un bord de dépendance est créé entre la transaction et tous ses prédécesseurs.
Remarque : Une fois que toutes les arêtes dépendantes ont été créées, elles ne peuvent pas être fusionnées et ne peuvent être exécutées que séquentiellement.
Processus d'exécution du DAG
Le thread principal initialisera d'abord un petit groupe de threads en fonction du nombre de cœurs matériels, et si les cœurs matériels échouent, aucun autre thread ne sera créé.
Lorsque le DAG n'est pas terminé, la boucle de thread attend que la transaction prête avec le degré d'entrée de 0 soit retirée de la méthode waitPop du DAG. Si la transaction à exécuter est retirée avec succès, la transaction sera exécutée. S'il échoue, le DAG a terminé son exécution et le thread se ferme.
Problèmes et solutions
Pour un même bloc, comment s'assurer que tous les nœuds ont terminé leur exécution et sont dans le même état (les trois nœuds racine correspondent) ?
FISCO BCOS vérifie que les triplets, c'est-à-dire la racine d'état, la racine de transaction et la racine de réception, sont égaux les uns aux autres pour déterminer si les états sont convenus. Une racine de transaction est une valeur de hachage calculée sur la base de toutes les transactions du bloc.
Tant que tous les nœuds de consensus traitent les mêmes données de bloc, la racine de la transaction doit être la même, ce qui est relativement facile à garantir. La clé est de s'assurer que l'état et la racine du reçu générés après la transaction sont les mêmes.
Il est bien connu que l'ordre d'exécution entre des instructions exécutées en parallèle sur des cœurs CPU différents ne peut pas être prédit à l'avance, et il en est de même pour les transactions exécutées en parallèle.
Dans le schéma d'exécution de transaction traditionnel, la racine d'état change une fois que chaque transaction est exécutée, et la racine d'état modifiée est écrite dans le reçu de transaction.
Une fois toutes les transactions exécutées, la racine de l'état final représente l'état actuel de la blockchain. Dans le même temps, une racine de reçu est calculée sur la base de tous les reçus de transaction.
On peut voir que dans l'implémentation traditionnelle, la racine d'état agit comme une variable globale partagée.
Lorsque les transactions sont exécutées en parallèle et dans le désordre, le calcul traditionnel de la racine d'état n'est plus applicable car les transactions sont exécutées dans un ordre différent sur différentes machines et la racine d'état finale n'est pas garantie d'être cohérente, ni la racine de réception garantie être cohérent.
Dans FISCO BCOS, les transactions sont d'abord exécutées en parallèle et l'historique du changement d'état de chaque transaction est enregistré. Une fois toutes les transactions exécutées, une racine d'état est calculée en fonction de l'historique.
Dans le même temps, la racine d'état dans l'accusé de réception de transaction devient la racine d'état finale après l'exécution de toutes les transactions, garantissant ainsi que les nœuds de consensus peuvent toujours parvenir à un accord même si les transactions sont exécutées en parallèle.
Comment déterminer si deux transactions sont dépendantes ?
Si deux transactions ne sont pas dépendantes mais sont jugées comme telles, cela entraînera une perte de performances inutile. A l'inverse, si les deux transactions réécrivent l'état d'un même compte mais sont exécutées en parallèle, l'état final du compte peut être incertain.
Par conséquent, la détermination de la dépendance est un problème important qui affecte les performances et peut même déterminer si la blockchain peut fonctionner correctement.
Dans une transaction de transfert simple, nous pouvons juger si deux transactions sont dépendantes en fonction des adresses de l'expéditeur et du destinataire. Prenez les trois transactions de transfert suivantes comme exemple, A→B, C→D et D→E.
Il est facile de voir que la transaction D→E dépend du résultat de la transaction C→D, mais la transaction A→B n'a rien à voir avec les deux autres transactions, elle peut donc être exécutée en parallèle.
Ce type d'analyse est vrai dans une blockchain qui ne prend en charge que les transferts simples, mais il peut ne pas être aussi précis dans une blockchain complète de Turing qui exécute des contrats intelligents, car nous ne savons pas exactement ce qui se passe dans un contrat de transfert écrit par l'utilisateur. . Voici ce qui pourrait arriver.
Il semble que la transaction de A→B n'ait rien à voir avec le statut du compte de C et D, mais dans l'implémentation sous-jacente de l'utilisateur, A est un compte spécial, et une certaine commission doit être déduite du compte de C pour chaque argent transféré via le compte de A.
Dans ce scénario, les trois transactions sont toutes liées, elles ne peuvent donc pas être exécutées en parallèle. Si les transactions sont divisées selon la méthode d'analyse des dépendances précédente, cela est susceptible de provoquer des erreurs.
Peut-on déduire automatiquement quelles dépendances existent réellement dans une transaction en fonction du contenu du contrat de l'utilisateur ? La réponse est non. Comme mentionné précédemment, il est difficile d'analyser les dépendances contractuelles et le processus d'exécution en analyse statique.
Dans FISCO BCOS, l'attribution des dépendances commerciales est laissée aux développeurs qui connaissent mieux le contenu du contrat. Plus précisément, les ressources mutuellement exclusives dont dépend la transaction peuvent être représentées par un ensemble de chaînes.
FISCO BCOS expose l'interface au développeur, qui définit les ressources dont dépend la transaction sous forme de chaînes et informe l'exécuteur de la chaîne.
L'exécuteur organisera automatiquement toutes les transactions du bloc dans le DAG de transaction en fonction des dépendances de transaction spécifiées par le développeur.
Par exemple, dans un contrat de transfert simple, le développeur spécifie simplement que la dépendance pour chaque transaction de transfert est {adresse de l'expéditeur + adresse du destinataire}.
De plus, si le développeur introduit une autre adresse tierce dans la logique de transfert, la dépendance doit être définie comme {adresse de l'expéditeur + adresse du destinataire + adresse tierce}.
Cette approche est intuitive, simple et générale, et s'applique à tous les contrats intelligents, mais elle augmente également la responsabilité sur les épaules du développeur.
Le développeur doit être très prudent lors de la spécification des dépendances de transaction. Si les dépendances ne sont pas écrites correctement, les conséquences sont imprévisibles.
Contrat-cadre parallèle
Afin que les développeurs puissent utiliser le cadre des contrats parallèles, FISCO BCOS a défini certaines spécifications pour la rédaction des contrats. Les spécifications sont les suivantes :
Parallèle mutuellement exclusif
La possibilité d'exécuter deux transactions en parallèle dépend du fait que les deux transactions s'excluent mutuellement. L'exclusion mutuelle fait référence à l'intersection de l'ensemble des variables de stockage de deux transactions.
Par exemple, dans un scénario de transfert d'actifs, une transaction est une opération de transfert entre utilisateurs. transfer(X, Y) représente l'interface de transfert de l'utilisateur X à l'utilisateur Y, et l'exclusion mutuelle est la suivante.
Paramètre mutuellement exclusif : Paramètre lié à l'opération de « lecture/écriture » de la variable de stockage du contrat dans l'interface du contrat. Prenez l'interface de transfert transfer(X, Y) par exemple. X et Y sont des paramètres mutuellement exclusifs.
Mutex : le contenu mutex spécifique extrait d'une transaction en fonction des paramètres mutex. Prenez l'interface de transfert transfer(X, Y) par exemple. Dans une transaction de transfert utilisant cette interface, le paramètre spécifique est transfer(A, B) et le mutex de cette opération est [A, B]. Pour une autre transaction, transfer(A, C) est appelé, et le mutex pour cette opération est [A, C].
Déterminer si deux transactions peuvent être exécutées en parallèle en même temps revient à déterminer si le mutex de deux transactions se croise. Les transactions dont les intersections sont vides peuvent être exécutées en parallèle.
FFISCO-BCOS propose deux manières de rédiger des contrats parallèles, des contrats précompilés et des contrats de solidité, seuls ces derniers étant décrits ici. Il en va de même pour les contrats pré-compilés.
Cadre Parallèle du Contrat de Solidité
Pour écrire un contrat de solidité parallèle, en plus de cela, faites simplement de ParallelContract.sol la classe de base pour les contrats que vous souhaitez mettre en parallèle. La méthoderegisterParallelFunction() est appelée pour enregistrer les interfaces qui peuvent être parallélisées.
Le code du contrat parallèle est le suivant :
pragma solidity ^0.4.25; //Precompile the contract interface contract ParallelConfigPrecompiled { function registerParallelFunctionInternal(address, string, uint256) public returns (int); function unregisterParallelFunctionInternal(address, string) public returns (int); } //The parallel contract base class needs to be registered and the subcontract needs to be implement enable or disable interface contract ParallelContract { ParallelConfigPrecompiled precompiled = ParallelConfigPrecompiled(0x1006); function registerParallelFunction(string functionName, uint256 criticalSize) public { precompiled.registerParallelFunctionInternal(address(this), functionName, criticalSize); } function unregisterParallelFunction(string functionName) public { precompiled.unregisterParallelFunctionInternal(address(this), functionName); } function enableParallel() public; function disableParallel() public; }
L'exemple suivant est un contrat de transfert rédigé sur la base d'un contrat-cadre parallèle :
pragma solidity ^0.4.25; import "./ParallelContract.sol"; // Introduce ParallelContract.sol contract ParallelOk is ParallelContract // useParallelContract as a base class { // Contract implementation mapping (string => uint256) _balance; // Global mapping // The mutually exclusive variables from and to are the first two parameters at the beginning of transfer (). It can be seen that the contract requirements are still very strict, which will make users uncomfortable to write function transfer(string from, string to, uint256 num) public { _balance[from] -= num; // From is the key of the global mapping, and is a mutually exclusive parameter _balance[to] += num; //// To is the key of the global mapping, and is a mutually exclusive parameter } // The mutex variable name comes first as an argument to the beginning of set() function set(string name, uint256 num) public { _balance[name] = num; } function balanceOf(string name) public view returns (uint256) { return _balance[name]; } // Register contract interfaces that can be parallel function enableParallel() public { // The function definition string (note that there are no Spaces after ",") and the first few arguments are mutex arguments (mutex arguments must be first when designing a function) //The number 2 indicates that the first two are mutex parameters, and the system decodes the mutex according to the function signature and abi registerParallelFunction("transfer(string,string,uint256)", 2); // critical: string string // registerParallelFunction("set(string,uint256)", 1); // critical: string } // Deregister the parallel contract interface function disableParallel() public { unregisterParallelFunction("transfer(string,string,uint256)"); unregisterParallelFunction("set(string,uint256)"); } }
Déterminer si l'interface peut être parallèle
Une interface de contrat parallèle doit satisfaire :
Aucun contrat externe n'est appelé.
Aucune autre interface de fonction n'est appelée.
Déterminer le paramètre Mutex
Avant de programmer une interface, déterminez les paramètres mutuellement exclusifs de l'interface. Les paramètres mutuellement exclusifs de l'interface sont mutuellement exclusifs aux variables globales. Les règles de détermination des paramètres mutuellement exclusifs sont les suivantes :
Si le mappage global est accessible par l'interface, la clé du mappage est le paramètre mutuellement exclusif.
Si le tableau global est accédé par l'interface, l'indice du tableau est le paramètre mutuellement exclusif.
Si l'interface accède à des variables globales de type simple. Toutes les variables globales d'un type simple partagent un paramètre mutex et utilisent des noms de variables différents comme objets mutex.
Par exemple, il existe plusieurs variables globales de types simples dans le contrat et différentes interfaces accèdent à différentes variables globales.
Si vous souhaitez mettre en parallèle différentes interfaces, vous devez définir un paramètre mutex dans le paramètre d'interface avec la variable globale modifiée pour indiquer quelle variable globale est utilisée lors de l'appel.
Lorsqu'il est appelé, le paramètre mutex reçoit activement le "nom de variable" modifié de la variable globale pour identifier le mutex de la transaction.
Par exemple : si setA(int x) modifie globalA en tant que paramètre global, setA doit être défini comme set(string aflag, int x) . Lorsqu'il est appelé, setA("globalA", 10) est passé. Utilisez le nom de variable “globalA” pour indiquer que le mutex de cette transaction est globalA .
Déterminer le type de paramètre et l'ordre
Après avoir déterminé des paramètres mutuellement exclusifs, déterminez le type de paramètre et l'ordre conformément aux règles. Les règles sont les suivantes:
Les paramètres d'interface sont limités à string, address, uint256 et int256 (d'autres types seront pris en charge à l'avenir).
Les paramètres mutuellement exclusifs doivent tous apparaître dans les paramètres d'interface.
Tous les paramètres mutuellement exclusifs sont au premier rang des paramètres d'interface.
On peut voir que la transaction parallèle de FISCO-BCOS dépend largement des spécifications des contrats rédigés par les utilisateurs.
Si les spécifications des contrats rédigés par les utilisateurs ne sont pas standardisées, le système procède à la hâte à une exécution parallèle, ce qui peut entraîner l'incohérence fondamentale des livres de comptes.
Khipu
Abstrait
Khipu estime qu'il n'est pas réaliste pour les utilisateurs d'identifier et d'étiqueter sans erreur la plage d'adresses qui créera des conflits statiques au moment de la rédaction du contrat. Cela contraste avec le point de vue de FISCO-BCOS.
La question de savoir si, où et dans quelles conditions la condition de concurrence apparaîtra ne peut être jugée que lorsque l'acquisition de la certitude implique l'état actuel.
Ce type de jugement, avec les langages de programmation contractuels actuels, rend presque impossible l'analyse statique du code pour obtenir des résultats complètement corrects et non manqués.
Khipu a fait une tentative plus complète pour résoudre ce problème et a achevé un processus pour le mettre en œuvre.
Régime général
Dans Khipu, chaque transaction dans le même bloc commence à partir de l'état mondial du bloc précédent, puis s'exécute en parallèle, enregistrant les trois conditions de course ci-dessus rencontrées le long de tous les chemins d'expérience idéaux lors de l'exécution.
Après la phase d'exécution parallèle se trouve la phase de fusion, lorsque les états du monde parallèles sont fusionnés un par un. Lors de la fusion d'une transaction, jugez d'abord si vous avez un conflit avec les conditions de concurrence précédemment fusionnées à partir des conditions statiques enregistrées.
Sinon, fusionnez directement. Si tel est le cas, la transaction est exécutée à nouveau en commençant par l'état précédent du monde qui a été fusionné.
Le dernier état mondial fusionné est vérifié par rapport au hachage du bloc. C'est la dernière ligne de défense. Si la vérification est incorrecte, la fusion précédente est abandonnée et le bloc est exécuté à nouveau.
Index de parallélisme
Ici, Khipu introduit un indice de parallélisme, qui fait référence à la proportion de transactions dans un bloc qui peuvent combiner directement les résultats sans avoir à être exécutées à nouveau.
L'observation par Khipu de la relecture d'Ethereum sur plusieurs jours du bloc de création au bloc le plus récent montre que ce ratio (parallélisme) peut atteindre 80% en moyenne.
En général, si les tâches informatiques peuvent être entièrement parallélisées, l'évolutivité d'une seule chaîne est infinie. Parce que vous pouvez toujours ajouter plus de cœurs de processeur à un nœud. Si ce n'est pas le cas, alors le taux maximum théorique est limité par le théorème d'Andal :
La limite à laquelle vous pouvez accélérer le système dépend de l'inverse des parties qui ne peuvent pas être parallélisées. Donc, si vous pouvez paralléliser 99%, vous pouvez accélérer jusqu'à 100 fois. Mais si vous ne pouvez atteindre qu'une parallélisation de 95 %, vous ne pouvez obtenir que jusqu'à 20 fois plus vite.
De toutes les transactions sur Ethereum, environ 80% peuvent être parallélisées et 20% ne le peuvent pas, donc la limite de vitesse de Khipu est d'environ 5 fois.
Marqueurs de conflit
En comprenant les instructions dans le code evm, il a été constaté qu'un nombre limité d'instructions avaient créé des processus de lecture et d'écriture pour le stockage, il était donc possible d'enregistrer ces processus de lecture et d'écriture pour former une collection de lecture et d'écriture, mais du code statique l'analyse n'a pas pu garantir que ces processus étaient enregistrés.
Par conséquent, il est nécessaire de pré-exécuter chaque transaction une fois lors du traitement de chaque bloc. Le processus de pré-exécution nous indique si les transactions sont lues et écrites sur le même compte ou stockage, et crée un readSet et un writeSet pour chaque transaction.
S'il y a 100 transactions dans la blockchain, alors ces 100 transactions peuvent être exécutées en parallèle via le pool de threads. Chaque contrat a le même état mondial initial, et 100 readSets et writeSets seront créés lors de l'exécution, ainsi que 100 nouveaux états chacun.
Lorsque la pré-exécution est terminée, la prochaine étape du traitement commence. Idéalement, si les 100 entrées readSet et writeSet ne sont pas en conflit, elles peuvent être fusionnées directement pour produire l'état mondial final de toutes les transactions du bloc. Cependant, la transaction n'est souvent pas si idéale.
La bonne façon de traiter cela est de comparer le readSet et le writeSet après l'exécution de la première transaction avec le readSet et le writeSet après l'exécution du deuxième contrat, et de voir s'ils ont lu et écrit le même compte ou le même stockage.
Si tel est le cas, cela signifie que les deux accords sont en conflit. Ensuite, la deuxième transaction commencera après l'achèvement de la première transaction et sera exécutée à nouveau.
De même, au fur et à mesure que la machine d'état de fusion se poursuit, l'ensemble de conflits continuera à s'accumuler, et tant que les transactions suivantes entreront en conflit avec les transactions précédentes, elles seront exécutées séquentiellement jusqu'à ce que toutes les transactions aient été exécutées.
Grâce à la relecture des transactions sur le réseau principal d'Ethereum, on constate que là où il y a beaucoup de conflits, la plupart des cas sont des échanges dans le même bloc avec des transactions interdépendantes, ce qui est également cohérent avec ce processus.
Processus général
Processus parallèle spécifique
Aptos
Abstrait
Aptos s'appuie sur le langage Move de Diem et MoveVM pour créer une chaîne à haut débit qui permet une exécution parallèle. L'approche d'Aptos est de détecter les associations tout en étant transparent pour les utilisateurs/développeurs.
Autrement dit, les transactions ne sont pas tenues d'indiquer explicitement quelle partie de l'état (emplacement mémoire) elles utilisent.
Régime général
Aptos utilise une version modifiée de la mémoire de transaction logicielle appelée Block-STM et implémente son moteur d'exécution parallèle basé sur .
Block-STM utilise MVCC (Multi-version Concurrency Control) pour éviter les conflits écriture-écriture. Toutes les écritures au même emplacement sont stockées avec leurs versions, qui contiennent leur TX-ID et le nombre de fois où l'écriture tx a été ré-exécutée.
Lorsqu'une transaction (tx) lit une valeur pour un emplacement mémoire, elle obtient la valeur écrite à partir de MVCC dans cet emplacement qui s'est produite avant tx avec la version associée pour déterminer s'il existe un conflit de lecture/écriture.
Dans Block-STM, les transactions sont pré-triées dans les blocs et sont divisées entre les threads du processeur pour une exécution parallèle pendant l'exécution. En exécution parallèle, on suppose qu'il n'y a pas de dépendances pour exécuter la transaction.
Les emplacements mémoire modifiés par la transaction sont enregistrés. Après l'exécution, vérifiez tous les résultats de la transaction. Lors de la validation, si une transaction est trouvée pour accéder à un emplacement mémoire modifié par une transaction précédente, la transaction est invalide.
Actualisez le résultat de l'échange, puis réexécutez l'échange. Ce processus est répété jusqu'à ce que toutes les transactions du bloc aient été exécutées. Block-STM accélère l'exécution lorsque plusieurs cœurs de processeur sont utilisés. L'accélération dépend de l'interdépendance des transactions.
On peut voir que le schéma utilisé par Aptos est à peu près similaire au Khipu mentionné ci-dessus, mais il existe quelques différences de mise en œuvre, qui sont détaillées comme suit :
Khipu utilise l'exécution parallèle et la vérification séquentielle pour les transactions intra-bloc. Cependant, Aptos met en œuvre une exécution et une vérification parallèles pour les transactions au sein du bloc. Ces deux schémas présentent des avantages et des inconvénients. Khipu est facile à mettre en œuvre et l'efficacité est légèrement inférieure. Grâce à Block-STM, Aptos utilise la synchronisation et le fonctionnement du signal dans de nombreux threads, ce qui est très efficace mais difficile à implémenter dans le code.
Étant donné que Move prend en charge l'adressage global des ressources de manière native, Aptos réorganisera les transactions, même à travers les blocs, lorsque cela est propice à une exécution parallèle. Aptos affirme que ce schéma peut non seulement améliorer l'efficacité du parallèle mais également résoudre le problème MEV. Cependant, il reste à déterminer si cela affectera l'expérience utilisateur.
Aptos stocke le jeu d'écriture résultant en mémoire pendant l'exécution pour une vitesse d'exécution maximale, puis l'utilise comme cache pour le prochain bloc à exécuter. Toute écriture répétée n'a besoin d'être écrite qu'une seule fois dans la mémoire stable.
Test de référence
Aptos a fait un benchmark correspondant après l'intégration du bloc-STM et a comparé l'exécution séquentielle et l'exécution parallèle d'un bloc de 10k transactions. Le résultat de la comparaison s'affiche comme suit :
On peut voir sur la figure ci-dessus que Block STM est 16 fois plus rapide que l'exécution séquentielle avec 32 threads en parallèle, et plus de 8 fois plus rapide en cas de conflit élevé.
Conclusion
Sur la base de la comparaison et de l'analyse ci-dessus, on peut conclure que certains schémas exigent que les utilisateurs écrivent le stockage selon des règles établies lors de la rédaction de contrats afin que les dépendances puissent être trouvées par une analyse statique et dynamique.
Solana et Sui utilisent des schémas similaires, mais la perception de l'utilisateur est différente. Ce schéma est essentiellement un changement de modèle de stockage pour obtenir de meilleurs résultats d'analyse.
Khipu et Aptos sont des schémas indépendants de l'utilisateur. Les frais généraux de l'exécution parallèle ne pèsent pas sur les développeurs, et ils n'ont pas besoin d'y penser lors de la rédaction de leurs contrats.
La machine virtuelle analyse dynamiquement les relations de dépendance avant exécution, mettant ainsi en œuvre l'exécution parallèle sans relations de dépendance.
Ceci est difficile à mettre en œuvre et le degré de parallélisme dépend dans une certaine mesure de la division comptable de la transaction. Lorsqu'il y a beaucoup de conflits de transactions, les performances se détériorent considérablement en réexécutant constamment.
Aptos a mentionné qu'ils optimiseront à l'avenir les contrats rédigés par les utilisateurs pour mieux analyser les dépendances et ainsi obtenir une exécution plus rapide.
La simple modification d'un schéma basé sur la série en un schéma parallèle peut apporter 3 à 16 fois une amélioration du débit transactionnel dans un environnement de chaîne publique, et si cela peut être combiné avec de grands blocs et de grandes limites de gaz, le débit L2 sera encore optimisé, potentiellement environ 100 fois.
D'un point de vue technique, concernant la mise en œuvre et l'efficacité, OlaVM adoptera très probablement le schéma Khipu plus une solution de modèle de stockage personnalisé, qui peut améliorer les performances tout en évitant la complexité causée par l'introduction de Block-STM et faciliter une meilleure optimisation de l'ingénierie.
Les références
FISCO-BCOS Github,
Khipu GitHub, GitHub -
Aptos GitHub, GitHub -
À propos de nous
Fondée en 2021 et propulsée par des développeurs blockchain de premier ordre, Sin7y est un incubateur de projets et une équipe de recherche sur la technologie blockchain qui explore les technologies les plus importantes et les plus avancées, notamment EVM, Layer2, cross-chain, privacy computing, solutions de paiement autonomes, etc. .
Nous travaillons actuellement sur un ZKVM compatible EVM, rapide et évolutif appelé OlaVM. Si vous souhaitez discuter avec nous, n'hésitez pas à rejoindre notre ou à nous envoyer un e-mail à .