8
votes

Optimiser la conversion entre la liste des coefficients entier et sa longue représentation entier

J'essaie d'optimiser une implémentation polynomiale de la mienne. En particulier, je traite des polynômes avec des coefficients modulo n (peut être > 2 ^ 64 ) et modulo un polynôme dans la forme x ^ r - 1 < / code> ( r est <2 ^ 64 ). Pour le moment, je représente le coefficient comme une liste d'entiers (*) et j'ai mis en place toutes les opérations de base de la manière la plus simple.

J'aimerais que l'exponentiation et la multiplication soient aussi vite que possible, et à Obtenez cela, j'ai déjà essayé différentes approches. Mon approche actuelle consiste à convertir les listes de coefficients en vastes entiers multiplier les entiers et déballer les coefficients.

Le problème est que l'emballage et le déballage prend beaucoup de temps.

SO. , Y a-t-il un moyen d'améliorer mes fonctions "pack / décompresser"? xxx

Notez que je fais pas choisir n , il s'agit d'une entrée de l'utilisateur et mon programme veut Prouvez sa primalité (à l'aide du test AKS), donc je ne peux donc pas le factoriser.


(*) J'ai essayé plusieurs approches:

  1. Utilisation d'un tableau NUMPY au lieu d'une liste et multipliez à l'aide de numpy.convolve . Il est rapide pour n <2 ^ 64 mais terriblement lent pour n> 2 ^ 64 [aussi je voudrais éviter d'utiliser des bibliothèques externes]
  2. en utilisant scipy.fftconvolve . Ne fonctionne pas du tout pour n> 2 ^ 64 .
  3. représente les coefficients comme des entiers du début (sans la convertir à chaque fois). Le problème est que je ne connais pas un moyen facile de faire le fonctionnement mod x ^ r -1 sans convertir l'entier en une liste de coefficients (qui défait la raison de l'utilisation de cette représentation).

17 commentaires

Vous devriez probablement réduire la portée de la question à une échelle de problème raisonnable et responsable.


Oui, je pensais cela aussi. Je vais modifier ma question quand j'ai du temps et soulignerai exactement ce que j'aimerais optimiser.


Je ne sais pas si cela résout tout le problème, mais si vous recherchez une "manipulation implicite de polynômes utilisant des BDDs supprimés zéro", vous trouverez une technique pour manipuler efficacement des polynômes, y compris les tests d'égalité.


Pouvez-vous utiliser une bibliothèque numérique ou une autre bibliothèque numérique?


@PRZEMO_LI: J'ai effectivement essayé d'utiliser numpy et numpy.convolve pour la multiplication (et il est écrit dans ma question), mais c'est en fait plus lent que cette mise en œuvre [prend en compte que je dois travailler avec de gros entiers, également pour les coefficients. Avec Numpy One-Taille de Word, Numpy est beaucoup plus rapide.]. @HAROLD: Je vais maintenant essayer de voir.


@Bakuriu Comment s'est-il passé? La solution ZDD est-elle applicable à ce problème?


@harold je n'ai pas essayé. N'a pas eu beaucoup de temps ces derniers temps. J'ai cherché cet article et je ne l'ai trouvé que sur la vente. Peut-être que vous savez s'il y a une version publiée gratuitement en ligne? Finalement je vais l'acheter. Peut-être avant que je vais me regarder moi-même.


@Bakuriu Vous allez ici: Cecs.uci .EDU / ~ Papiers / Compendium94-03 / Papiers / 1995 / EDT95 / PDFFI LES / ... C'est un court papier qui n'explique pas les écrous et les boulons d'un BDD de 0-supprimé, mais vous pouvez trouver que autre part


Ouah merci. J'y regarderai quand j'ai le temps (et malheureusement, cela signifie la semaine prochaine 'parce que je suis occupé ce week-end).


@HAROLD J'ai essayé de jouer avec ZBDDS mais je ne pense pas qu'ils sont très applicables à mon cas d'utilisation. Le problème est qu'il n'est pas facile de mettre en œuvre des opérations de modulo sur cette représentation. Quoi qu'il en soit, je pense qu'ils sont vraiment intéressants et probablement vraiment utiles dans d'autres contextes.


@Bakuriu Oh, dommage, je pensais qu'ils étaient prometteurs


Connexes: Aks Prime Algorithme à Python


@JSEbastian probablement que le lien pourrait être utile pour d'autres personnes qui souhaitent mettre en œuvre des Aks, mais c'est de l'aide peu pour moi, car il n'y a rien de python liée à la réponse et c'est ce qui m'intéresse. J'ai déjà un Mise en œuvre de silex décente, je tiens simplement à pousser l'approche pure-python à ses limites.


@Bakuriu: avis d'optimisation qui s'applique également à Pure Python: faire moins . En particulier, vous pouvez utiliser des algorithmes / représentation de données qui ne nécessitent pas de conversion fréquente, c'est-à-dire, si vous lisez les liens (qu'ils utilisent un pseudo-code, C ++ ou une autre langue), vous pourriez trouver quelque chose qui peut aider Vous devez éliminer la fonction ci-dessus de votre question. Heure d'exécution du code qui n'est pas là est zéro . Même si vous ne trouvez rien; Une meilleure compréhension et une meilleure connaissance des différentes approches du même problème pourrait vous donner d'autres idées d'optimisation.


@ J.f.sebastian Le fait est que je les ai déjà lus et j'ai essayé ces approches, des Ans que j'ai découvert que mon approche est plus rapide.


Je suppose que recommander Pypy ne vous aidera pas beaucoup. C'est exactement le type de code que CPPHON est extrêmement mauvais et pyypy est très bon à. Vous devriez obtenir des performances de ballpark si vous gérez bien vos allocations de mémoire.


@Antsaasma, j'ai essayé le code sur pypy et j'ai constaté qu'il était significativement plus lent que cpython. Mais je ne suis pas un utilisateur pypy, alors j'ai probablement écrit le code d'une manière que Pypy ne gère pas bien. Quoi qu'il en soit, ma question est beaucoup plus sur CPPHON et «l'optimisation des algorithmes» que d'utiliser une implémentation spécifique.


4 Réponses :


2
votes

Sauf si vous faites cela pour apprendre, pourquoi réinventer la roue? Une approche différente serait d'écrire un wrapper python à une autre bibliothèque ou programme polynomial, si une telle enveloppe n'existe pas déjà.

essayez pari / gp. C'est étonnamment rapide. J'ai récemment écrit un code C personnalisé, qui m'a pris deux jours pour écrire et que je ne suis arrivé que 3 fois plus rapide qu'un script de deux lignes pari / gp. Je parierais qu'un code Python appelant Pari se terminerait comme plus rapide que tout ce que vous implémentez dans Python seul. Il y a même un module pour appeler Pari de Python: https://code.google.com/p/pari-python/


2 commentaires

Je cherche de telles optimisations parce que je aussi veux apprendre. J'ai déjà écrit une extension C qui utilise le bibliothèque FLINT pour effectuer les calculs et il est de 10 à 80 fois plus rapide en fonction de la tâche (et je connais déjà un moyen d'optimiser cette implémentation C, en particulier depuis que je connais la taille maximale des polynômes, je pourrais utiliser les fonctions préfixées _ et évitez beaucoup de réaffectation). Merci d'avoir souligné Pari / GP, je ne le savais pas et je vais y regarder. Quoi qu'il en soit, j'apprécierais une implémentation pure-python efficace des polynômes.


Assez juste! Probablement ce que j'ai écrit aurait dû être un commentaire et non une réponse - je m'habitue toujours à empiler débordement.



2
votes

Vous pouvez essayer d'utiliser Systèmes de numéro de résiduellement pour représenter les coefficients de votre polynôme. Vous diviseriez également vos coefficients en entiers plus petits que vous le faites maintenant, mais vous n'avez pas besoin de les convertir à un énorme entier pour faire des multiplications ou d'autres opérations. Cela ne devrait pas nécessiter beaucoup d'effort de reprogrammation.

Le principe de base des systèmes de numéros résiduels est la représentation unique des nombres utilisant des arithmétiques modulaires. Toute la théorie autour des RNS vous permet de faire vos opérations sur les petits coefficients.

EDIT: Un exemple rapide:

Supposons que vous représentais vos gros coefficients dans un RNS avec moduli 11 et 13. Vos coefficients se composent tous de 2 petits entiers (<11 et <13) pouvant être combinés à l'original (grand) entier.

Supposons que votre polynôme est à l'origine de 33x² + 18x + 44. Dans RNS, les coefficients seraient respectivement (33 MOD 11, 33 MOD 13), (18 MOD 11,18 MOD 13) et (44 MOD 11, 44 MOD 13) => (0,7), (7,5) et (0,5).

Multipliez votre polynôme avec une constante peut alors être effectuée en multipliant chaque petit coefficient à cette constante et à le modulo.

Dites que vous multipliez par 3, vos coefficients deviendront (0,21 MOD 13) = (0,8), (21 MOD 11,15 MOD 13) = (10) et (0 MOD 11,15 MOD 13) = (0,2). Il n'a pas été nécessaire de convertir les coefficients vers leur gros entier.

Pour vérifier si notre multiplication a fonctionné, nous pouvons convertir les nouveaux coefficients à leur grande représentation. Cela nécessite «résoudre» chaque ensemble de coefficients en tant que système modulaire. Pour les premiers coefficients (0,8), nous devrions résoudre X MOD 11 = 0 et X MOD 13 = 8. Cela ne devrait pas être trop difficile à mettre en œuvre. Dans cet exemple, vous pouvez voir que x = 99 est une solution valide (modulo 13 * 11)

Nous obtenons ensuite 99x² + 54x + 132, le polynôme multiplié correct. Multiplier avec d'autres polynômes est similaire (mais vous oblige à multiplier les coefficients les uns avec les autres de manière parire). Il en va de même pour ajouter.

Pour votre cas d'utilisation, vous pouvez choisir votre n sur la base du nombre de coefficients que vous souhaitez ou de leur taille.


6 commentaires

Pouvez-vous me signaler à un article / livre qui explique un peu plus ces RNS? Quoi qu'il en soit, vous voulez dire que je devrais représenter les coefficients par un certain nombre d'entiers plus petits, puis opérer avec un éventail de ces plus petits nombres (ce qui me permettrait probablement d'utiliser des numéros)?


Je n'ai aucune référence spécifique, mais il y a de nombreux tutoriels là-bas comme Ce , Ceci et Ceci . N'importe lequel d'entre eux devrait vous aider à démarrer. Je pense que NUMPY devrait être réalisable.


La base de l'algorithme est le Théorème de reste chinois qui assure la représentation unique d'un nombre en RNS . Un Preuve est également assez intéressant si vous voulez En savoir plus à ce sujet.


Pourriez-vous expliquer comment effectuer le fonctionnement modulo n de manière efficace? Pour moi, il semble que je doive convertir chaque coefficient en décimal, prenez le modulo, puis re-convertir en RNS. Mais ce type de conversion n'est pas efficace.


J'ai ajouté un exemple. Vous devriez faire toutes vos opérations sur les coefficients (..,.) Et les convertir uniquement à leur grande taille lorsque vous en avez besoin.


Désolé mais je doute que c'est une solution appropriée pour moi. Le N est donné comme entrée par l'utilisateur et mon programme souhaite prouver sa primalité, je ne peux donc pas simplement le facaliser pour que le petit moduli puisse avoir la représentation des RNS. Je dois choisir un ensemble de modules de telle sorte que je puisse représenter tous les numéros jusqu'à n , puis toutes les opérations doivent être modulo n , et je pense que ce n'est pas efficace avec cette représentation.



1
votes

J'ai trouvé un moyen d'optimiser les conversions, même si j'espère toujours que quelqu'un pourrait m'aider à m'améliorer encore plus, et j'espère trouver une autre idée intelligente.

essentiellement ce qui ne va pas avec ces fonctions est qu'ils ont une sorte de comportement d'allocation de mémoire quadratique, lors de l'emballage de l'entier ou de le déballer. (Voir Ce Post de Guido Van Rossum pour un autre exemple de ce type de comportement ).

Après avoir réalisé cela, j'ai décidé de donner un essai avec le principe Divide et impera, et j'ai obtenu des résultats. Je divisez simplement la matrice en deux parties, convertissez-les séparément et rejoignez-le des résultats (plus tard, je vais essayer d'utiliser une version itérative similaire au F5 dans Rossum's Post [Modifier: il ne semble pas être beaucoup plus rapide]).

Les fonctions modifiées: xxx

et les résultats: xxx < P> Comme vous pouvez le voir, cette version donne une assez grande vitesse à la conversion, à partir de 4 à 8 fois plus rapide (et plus grand l'entrée, plus grande est la vitesse supérieure). Un résultat similaire est obtenu avec la deuxième fonction: xxx

J'ai essayé d'éviter plus de réallocation de mémoire dans la première fonction qui passe autour des index de début et de fin et d'éviter de trancher la tranchée, mais Il s'avère que cela ralentit bien la fonction pour de petites entrées et il est un peu plus lent pour les intrants en temps réel. Peut-être que je pourrais essayer de les mélanger, même si je ne pense pas que j'obtiendrai beaucoup de meilleurs résultats.


J'ai édité ma question au cours des dernières années, certaines personnes m'ont donné des conseils Avec un but différent, ce que j'aurais demandé récemment. Je pense qu'il est important de clarifier un peu les résultats indiqués par différentes sources dans les commentaires et les réponses, de sorte qu'ils puissent être utiles aux autres personnes qui cherchent à mettre en œuvre des polynômes rapides et ou des tests Aks.

  • Comme j.f. Sebastian a souligné que l'algorithme Aks reçoit de nombreuses améliorations, et essayer de mettre en œuvre une ancienne version de l'algorithme entraînera toujours un programme très lent. Cela n'exclut pas le fait que si vous avez déjà une bonne implémentation d'AKS, vous pouvez accélérer l'amélioration des polynômes.
  • Si vous êtes intéressé par les coefficients modulo un petit n (lecture: numéro de format de texte) et que vous ne dérangeez pas les dépendances externes, optez pour numpy et utilisez numpy.convolve ou scipy.fftconvolve pour la multiplication. Ce sera beaucoup plus rapide que tout ce que vous pouvez écrire. Malheureusement, si n n'est pas la taille du mot, vous ne pouvez pas utiliser sciped.fftconvolve du tout, ainsi que numpy.convolve devient lent comme l'enfer. < / li>
  • Si vous n'avez pas à faire des opérations de modulo (sur les coefficients et sur le polynôme), utilisez probablement la ZBDDS est une bonne idée (comme indiqué par Harold), même si je ne promets pas de résultats spectaculaires [même si Je pense que c'est vraiment intéressant et vous devriez lire le papier de Minato].
  • Si vous n'avez pas à faire des opérations de modulo sur les coefficients, utilisez probablement probablement une représentation RNS, comme indiqué par l'origine, est une bonne idée. Ensuite, vous pouvez combiner plusieurs réseaux numpy pour fonctionner efficacement.
  • Si vous voulez une implémentation pure-python de polynômes avec coefficient modulo un gros n , alors ma solution semble être la plus rapide. Même si je n'avais pas essayé de mettre en œuvre la multiplication FFT entre les tableaux de coefficients de Python (que peut être plus rapide).

0 commentaires

2
votes

Que diriez-vous de mettre en œuvre directement des polynômes entier de précision arbitraire en tant que liste de tableaux numpus?

Permettez-moi d'expliquer: dites que votre polynôme est p a p x p . Si le gros entier A P peut être représenté sous la forme d'un p = σ k a p, k 2 64 K alors le tableau K TH NUMPY contiendra le 64 bits Int A P, K à la position p.

Vous pouvez choisir des matrices denses ou rares en fonction de la structure de votre problème.

La mise en œuvre des opérations d'addition et scalaires ne consiste à viller à vectoriser la mise en œuvre de Bignum des mêmes opérations.

Multiplication pourrait être traité comme suit: ab = σ p, k, p ', k' a p, k b p ', k' < / sub> 2 64 (k + k ') x p + p' . Donc, une implémentation naïve avec des érayes denses pourrait conduire à un journal 64 (n) 2 appels vers numpy.convole ou scipy.fftconvolve < / code>.

L'opération MODULO doit être facile à appliquer car il s'agit d'une fonction linéaire du terme main gauche et du terme à droite contient de petits coefficients.

edit Voici quelques explications supplémentaires

au lieu de représenter le polynôme sous forme de liste de nombres de précision arbitraires (représentés eux-mêmes comme des listes de "chiffres" 64 bits), transpose la représentation de sorte que:

  • Votre polynôme est représenté comme une liste de tableaux
  • La matrice K TH contient le K TH "chiffre" de chaque coefficient

    Si que seuls quelques-uns de vos coefficients sont très volumineux, les matrices auront la plupart du temps 0 à celles-ci afin que cela puisse valoir la peine d'utiliser des tableaux de rattrapage.

    Appelez A P, K LE K TH DIGIT du p TH coefficient.

    Notez l'analogie avec de grandes représentations entier: où un grand nombre d'entiers serait représenté comme

    x = σ k x k 2 64 k

    Votre polynôme A est représenté de la même manière que

    a = σ k a k 2 64 k A k = σ k a p, k x p

    Pour implémenter l'addition, vous prétendez simplement que votre liste de tableaux est une liste de chiffres simples et de mettre en œuvre l'addition comme d'habitude pour les grands entiers (observez-vous pour remplacer si alors conditionnels par Numpy.Lowle ).

    Pour implémenter la multiplication, vous trouverez que vous devez créer un journal 64 (n) 2 multiplications polynomiales.

    Pour mettre en œuvre l'opération MODULO sur les coefficients, c'est à nouveau un cas simple de traduction de l'opération de modulo sur un grand nombre d'entiers.

    Pour prendre le modulo par un polynôme avec de petits coefficients, utilisez la linéarité de cette opération:

    un mod (x r - 1) = (σ k a k 2 64 k ) mod (X r - 1)

    = σ k 2 64 k (A k mod (x r - 1))


6 commentaires

Pourriez-vous expliquer un peu plus cette idée? En outre, vous avez écrit 2 ^ 64.k , le point est une multiplication? Quoi qu'il en soit, comme je l'ai dit que j'aimerais faire cela sans utiliser des bibliothèques tierces, votre choix est néanmoins une solution intéressante.


Oui, Dot était la multiplication, je l'ai supprimé. J'ai ajouté des détails, je ne sais pas quelle partie a besoin de plus de clarification ..


Ok, je peux voir la grande image clairement maintenant, mais, Hélas, jusqu'au mardi prochain, je suis vraiment occupé et je n'aurai pas le temps de mettre en œuvre et d'étudier votre solution.


Oui, vous avez votre travail découpé pour vous si vous descendez cette route, bien que si votre objectif apprenait, vous apprendrez beaucoup! Cependant, depuis que j'ai trouvé cette question vraiment intéressante, j'ai écrit une implémentation partielle, peut-être que cela vous fera commencer. Je posterai quelques-uns ici ..


J'ai essayé de mettre en œuvre cela maintenant (désolé d'être si tard!), Mais il y a quelque chose que je ne comprends pas. Par ce que je comprends, chaque fois que je fais rp, k = ap, k + bp, k et j'ai un résultat plus gros que 2 ^ 64 je devrais ajouter le transport à < Code> RP, K + 1 , mais NUMPY ne se plaingera pas à ce cas et je serais donc pile vérifier tous les coefficients de suite, essayez de comprendre s'il y avait un débordement à la main et a finalement ajouté manuellement le transporter. Y a-t-il un moyen plus intelligent de le faire avec NUMPY?


Oui, l'idée générale est que vous aurez besoin de gérer le débordement. Mais cela peut être beaucoup plus simple en prenant une taille de chiffre de demi-mot et en le stockant dans un mot complet. J'ai mis en place un exemple ici: Github.com/gsiidier/bigpoly - regardez les classes bigpoly.halfint et demi-propre. Il y a toujours des bugs avec une multiplication polynomiale, due au débordement, alors accrochez-vous. La classe Poly64 montre l'autre (de manière plus compliquée) de le faire en codant explicitement la logique de transport.