La comparaison et l'optimisation des vitesses BASIC et C et assembleur jouent avec IchigoJam

introduction

J'ai comparé la vitesse des programmes créés avec BASIC, C et l'assembleur avec IchigoJam, et j'ai essayé de l'optimiser pour le jeu. Le programme est un programme BASIC qui affiche Mandelbrot réglé avec IchigoJam [Creative Commons License](https://ja.wikipedia.org/ wiki /% E3% 82% AF% E3% 83% AA% E3% 82% A8% E3% 82% A4% E3% 83% 86% E3% 82% A3% E3% 83% 96% E3% 83% BB % E3% 82% B3% E3% 83% A2% E3% 83% B3% E3% 82% BA% E3% 83% BB% E3% 83% A9% E3% 82% A4% E3% 82% BB% E3 Il y a une personne qui est publiée à% 83% B3% E3% 82% B9), et j'utilise ceci parce que c'était pratique pour cette comparaison. Le système de traitement de chaque langue utilisée est

Langue Système de traitement Édition Adresse publique
BASIC IchigoJam BASIC ver 1.2.1 pour clavier américain http://ichigojam.net/download.html
C gcc 5.4.1 https://launchpad.net/gcc-arm-embedded
C clang 3.9.1 http://releases.llvm.org/download.html
assembleur GNU Binutils(Ce qui vient avec gcc) 2.26.2 https://launchpad.net/gcc-arm-embedded

J'utilise ce qui précède. BASIC Entrez Original Source dans IchigoJam, supprimez la 20e ligne, ajoutez la ligne suivante et exécutez-la.

python


5 CLS:CLT
50 LC0,23:?TICK()

Mesurez le temps entre l'effacement de l'écran et l'affichage de l'ensemble de Mandelbrot. Le temps est mesuré par la fonction TICK () de IchigoJam BASIC, et l'unité est d'environ 1/60 seconde. Le résultat de l'exécution est le suivant. mandelbrot.basic.1.jpg Le résultat est 31008, mais comme il est en unités d'environ 1/60 de seconde, si vous le convertissez en temps, 31008 * 1/60 = 516,8, ce qui signifie qu'il a fallu environ 8 minutes et 37 secondes. L'auteur de ce programme est écrit dans Article que "il faut environ 15 minutes pour tout dessiner", mais au moment où cet article a été écrit Il semble que la vitesse d'exécution d'IchigoJam BASIC ait été grandement améliorée par la mise à jour de la version. C

Essayez gcc

Ce qui suit est un portage du programme BASIC ci-dessus vers C fidèlement, sauf pour la taille de la variable.

mandelbrot.c


#include <stdint.h>

int mandelbrot(int16_t arg, uint8_t* ram, const uint8_t* font)
{
    (void)arg; (void)font;
    int n = 32;
    int w = 64, h = 48;
    int a = 2 << 6, b = a * a, c = (3 << 12) / w;
    for (int y = 0; y < h; y++) {
        int v = (y - h / 2) * c;
        for (int x = 0; x < w; x++) {
            int u = (x - (w * 3) / 4) * c;
            uint8_t* z = &ram[0x900 + (x / 2) + (y / 2) * 32];
            int j = 1 << ((y % 2) * 2 + (x % 2));
            int k = j | 0x80 | *z;
            *z = k;
            int p = u, q = v, i = 0;
            do {
                int r = p / 64; p = r * r; if (p < 0) goto l40;
                int s = q / 64; q = s * s; if (q < 0) goto l40;
                if (p + q < 0 || p + q > b) goto l40;
                p = p - q + u;
                q = 2 * r * s + v;
            } while (++i < n);
            *z = k ^ j;
          l40:;
        }
    }
    return 0;
}

Compilez-le en langage machine avec le script Makefile et Perl suivant, convertissez-le en formulaire de commentaire BASIC et créez mandelbrot.apo. L'option d'optimisation du compilateur spécifie «-Ofast» car elle met l'accent sur la vitesse.

Makefile


target: mandelbrot.apo

mandelbrot.s: mandelbrot.c
	arm-none-eabi-gcc -Wall -W -mcpu=cortex-m0 -mthumb -fpic -Ofast -S $<

mandelbrot.o: mandelbrot.s
	arm-none-eabi-as $< -o$@ -a=$(<:.s=.prn)

mandelbrot.x: mandelbrot.o
	arm-none-eabi-ld --entry 0x700 -Ttext 0x700 $< -o $@ -Map $(@:.x=.map)

mandelbrot.mot: mandelbrot.x
	arm-none-eabi-objcopy -O srec $< $@

mandelbrot.apo: mandelbrot.mot
	perl hex2apo.pl $< > $@

hex2apo.pl


#!/usr/bin/perl
$start = 0xffffffff;
$end = 0x00000000;
while(<>) {
    $S = substr($_, 0, 1);
    $type = substr($_, 1, 1);
    die if ($S != "S" || !($type >= 0 && $type <= 9));
    $size = hex(substr($_, 1 + 1, 2));
    for ($sum = 0, $i = 0; $i <= $size; $i++) {
        $sum += hex(substr($_, 1 + 1 + 2 * $i, 2));
    }
    die if (($sum & 0xff) != 0xff);
    if ($type >= 1 && $type <= 3) {
        $size = $size - 1 - ($type + 1);
        $address = hex(substr($_, 1 + 1 + 2, 2 * ($type + 1)));
        if ($start > $address) {
            $start = $address;
        }
        if ($end < $address + $size - 1) {
            $end = $address + $size - 1;
        }
        for ($i = 0; $i < $size; $i++) {
            $mem[$address + $i] = hex(substr($_, 1 + 1 + 2 + 2 * ($type + 1) + 2 * $i, 2));
        }
    }
}
$line = 10;
for ($i = $start; $i <= $end;) {
    printf("%d '", $line);
    for ($j = 0; $j < 15; $j++) {
        $n = 0;
        for ($k = 0; $k < 8; $k++) {
            $n = ($n << 8) & 0xff00;
            if ($k < 7) {
                $n += $mem[$i++];
            }
            &putc((($n >> ($k + 1)) & 0x7f));
            if ($i > $end) {
                if ($k < 7) {
                    &putc((($n << (6 - $k)) & 0x7f));
                }
                last;
            }
        }
        if ($i > $end) {
            last;
        }
    }
    printf("\n");
    $line += 1;
}

sub putc {
    my ($c) = @_;
    $c += 0x21;
    if ($c >= 0x7f) {
        $c += (0xa1 - 0x7f);
    }
    printf("%c", $c);
}

mandelbrot.apo


10 'Sa NL Ki Sz-eD4IekP, Um2a#=?"&j"%^y%Chi Hn2aj)%c&n-㽬 9bW")q!J;M!$ja%Z{;ACNrcA-r-#n7 ・';3Ig#GmUEr-{DH)a*-Ga"ゥ g1%f-"23 js#Cga)jUQ'ヲ
11 'D'B#"1UdBra:1#G*C CDA%ャ!!i2iU9%Sae-1 a"Qxq ァ&Se BZ,![A1D.9 / 12neA1!O!bBwiYO、KfカXA*|J.9D9O、4ァUウ!i-ca eX.)|DQ'ァ"Oer ")seQ7X "C!GQMm ”L
12 's clé A#!?"].image)K;egvr.,Ju J-"nu'39GウエZ"/Kochi!UNE`Tuu

Ce qui suit est une version fusionnée avec un programme BASIC qui lit ceci, l'écrit en mémoire, l'exécute en tant que programme en langage machine et utilise TICK () pour mesurer et afficher le temps d'exécution.

text:mandelbrot.gcc.1.bas


1 ' Mandelbrot set(gcc)
2 Z=#C03:Y=#700
3 X=0
4 IFPEEK(Z)=39X=X-1:W=33-PEEK(Z-X):IFW<1V=V<<7-W+W/96*34:POKEY,V>>(X&7):Y=Y+(X&7<7):GOTO4
5 Z=Z+PEEK(Z-1)+4:IFPEEK(Z-3)+PEEK(Z-2)GOTO3
6 CLS:CLT:Z=USR(#700,0):LC0,23:?TICK()
10 'Sa NL Ki Sz-eD4IekP, Um2a#=?"&j"%^y%Chi Hn2aj)%c&n-㽬 9bW")q!J;M!$ja%Z{;ACNrcA-r-#n7 ・';3Ig#GmUEr-{DH)a*-Ga"ゥ g1%f-"23 js#Cga)jUQ'ヲ
11 'D'B#"1UdBra:1#G*C CDA%ャ!!i2iU9%Sae-1 a"Qxq ァ&Se BZ,![A1D.9 / 12neA1!O!bBwiYO、KfカXA*|J.9D9O、4ァUウ!i-ca eX.)|DQ'ァ"Oer ")seQ7X "C!GQMm ”L
12 's clé A#!?"].image)K;egvr.,Ju J-"nu'39GウエZ"/Kochi!UNE`Tuu

Le résultat de la saisie dans IchigoJam et de son exécution est le suivant. mandelbrot.gcc.0.jpg La valeur numérique du résultat est trop petite et l'amplitude de la valeur après la virgule décimale tronquée au moment de la mesure est trop grande comme une erreur pour évaluer le résultat, alors changez de programme

python


6 CLS:CLT:FORI=1TO100:Z=USR(#700,0):NEXT:LC0,23:?TICK()

Exécutez 100 fois et évaluez le résultat. mandelbrot.gcc.1.jpg Il dit 293, mais supprimez Z = USR (# 700,0) de la ligne précédente

python


6 CLS:CLT:FORI=1TO100:NEXT:LC0,23:?TICK()

Le résultat de l'exécution de la boucle seulement 100 fois sans exécuter la partie langage machine est mandelbrot.100loop.jpg Puisqu'il est 9, (293-9) / 100 = 2,84 est le temps d'exécution approximatif par exécution.

Essayez clang

La ligne appelant arm-none-eabi-gcc dans le Makefile précédent

	clang -Wall -W -target arm-eabi -mcpu=cortex-m0 -fpic -Ofast -S $<

Vous pouvez changer le compilateur C à utiliser pour clang en changeant en. L'option d'optimisation spécifie «-Ofast» comme dans gcc. Le programme BASIC qui incorpore le code généré par clang est le suivant.

text:mandelbrot.clang.1.bas


1 ' Mandelbrot set(clang)
2 Z=#C03:Y=#700
3 X=0
4 IFPEEK(Z)=39X=X-1:W=33-PEEK(Z-X):IFW<1V=V<<7-W+W/96*34:POKEY,V>>(X&7):Y=Y+(X&7<7):GOTO4
5 Z=Z+PEEK(Z-1)+4:IFPEEK(Z-3)+PEEK(Z-2)GOTO3
6 CLS:CLT:FORI=1TO100:Z=USR(#700,0):NEXT:LC0,23:?TICK()
10 'Sa NRl!'C!1"S!C<!A#E-%:V3a-#s/)^c9&!Faire=ャ'*2g "N!"(153bE!#JB.3!!5a,>Clé d-2cIA$mB$!-"'~!jw5<EU#nCD!}{=a%FgU=!c:Bb o 1+u*:%N59 ャ 8j
11 '-!$1Ua=g&H!z i mP<(]ヲ*ye]g\1 Donc^Alors>ァ-ャ ・ 4 ・:idq'BZui(1B$\(!"-<Pq i!!KV%-<Dc"Xl|%G!Shi k&!'m!O!m{=A,X-)#Oui/-&kGe!A'M?,O$~ツツツ「ツセAlors"}
12 'Tatsui p Tachi"!!!!

Si vous faites cela, le résultat sera mandelbrot.clang.1.jpg Il est devenu 307. , (307-9) / 100 = 2,98 est le temps d'exécution approximatif par exécution. Malheureusement pour la secte clang, ce test était légèrement (environ 5%) inférieur à gcc.

Essayez l'optimisation manuelle

Dans ce Makefile, la source assembleur est sortie une fois au moment de la construction, assemblée et liée. Le contenu de la source assembleur généré dans le cas du gcc précédent est le suivant.

mandelbrot.s


	.syntax unified
	.cpu cortex-m0
	.fpu softvfp
	.eabi_attribute 23, 1
	.eabi_attribute 24, 1
	.eabi_attribute 25, 1
	.eabi_attribute 26, 1
	.eabi_attribute 30, 2
	.eabi_attribute 34, 0
	.eabi_attribute 18, 4
	.thumb
	.syntax unified
	.file	"mandelbrot.c"
	.text
	.align	2
	.global	mandelbrot
	.code	16
	.thumb_func
	.type	mandelbrot, %function
mandelbrot:
	@ args = 0, pretend = 0, frame = 24
	@ frame_needed = 0, uses_anonymous_args = 0
	push	{r4, r5, r6, r7, lr}
	mov	r7, fp
	mov	r6, r10
	mov	r4, r8
	mov	r5, r9
	ldr	r3, .L12
	push	{r4, r5, r6, r7}
	mov	r8, r3
	movs	r3, #0
	sub	sp, sp, #28
	str	r3, [sp, #20]
	movs	r3, #128
	lsls	r3, r3, #7
	movs	r7, #63
	mov	r10, r3
	str	r1, [sp, #16]
.L6:
	movs	r1, #1
	ldr	r2, [sp, #20]
	ldr	r6, .L12+4
	asrs	r3, r2, #1
	lsls	r3, r3, #5
	ands	r1, r2
	str	r3, [sp, #8]
	lsls	r3, r1, #1
	str	r3, [sp, #12]
	movs	r3, #0
	mov	fp, r3
.L5:
	movs	r2, #144
	mov	r3, fp
	lsls	r2, r2, #4
	mov	ip, r2
	ldr	r2, [sp, #8]
	asrs	r3, r3, #1
	add	r3, r3, ip
	mov	ip, r2
	ldr	r2, [sp, #16]
	add	r3, r3, ip
	mov	ip, r2
	mov	r2, fp
	add	ip, ip, r3
	movs	r3, #1
	ands	r3, r2
	ldr	r2, [sp, #12]
	movs	r1, r6
	mov	r9, r2
	movs	r2, #1
	add	r3, r3, r9
	lsls	r2, r2, r3
	mov	r9, r2
	movs	r2, #128
	mov	r3, r9
	orrs	r2, r3
	mov	r3, ip
	ldrb	r3, [r3]
	movs	r4, #32
	orrs	r2, r3
	mov	r3, r8
	str	r2, [sp, #4]
	str	r3, [sp]
	b	.L4
.L2:
	subs	r1, r1, r2
	ldr	r2, [sp]
	lsls	r0, r0, #1
	mov	r8, r2
	muls	r3, r0
	subs	r4, r4, #1
	adds	r1, r6, r1
	add	r3, r3, r8
	cmp	r4, #0
	beq	.L11
.L4:
	asrs	r0, r1, #31
	asrs	r2, r3, #31
	ands	r0, r7
	ands	r2, r7
	adds	r1, r0, r1
	adds	r3, r2, r3
	asrs	r0, r1, #6
	asrs	r3, r3, #6
	movs	r1, r0
	movs	r2, r3
	muls	r1, r0
	muls	r2, r3
	adds	r5, r1, r2
	cmp	r5, r10
	ble	.L2
	ldr	r3, [sp]
	mov	r2, sp
	mov	r8, r3
	mov	r3, ip
	ldrb	r2, [r2, #4]
	strb	r2, [r3]
.L3:
	movs	r3, #1
	mov	ip, r3
	add	fp, fp, ip
	mov	r3, fp
	adds	r6, r6, #192
	cmp	r3, #64
	bne	.L5
	movs	r2, #192
	mov	ip, r2
	ldr	r3, [sp, #20]
	add	r8, r8, ip
	adds	r3, r3, #1
	str	r3, [sp, #20]
	cmp	r3, #48
	bne	.L6
	movs	r0, #0
	add	sp, sp, #28
	@ sp needed
	pop	{r2, r3, r4, r5}
	mov	r8, r2
	mov	r9, r3
	mov	r10, r4
	mov	fp, r5
	pop	{r4, r5, r6, r7, pc}
.L11:
	mov	r2, r9
	ldr	r3, [sp, #4]
	eors	r3, r2
	mov	r2, ip
	strb	r3, [r2]
	b	.L3
.L13:
	.align	2
.L12:
	.word	-4608
	.word	-9216
	.size	mandelbrot, .-mandelbrot
	.ident	"GCC: (GNU Tools for ARM Embedded Processors) 5.4.1 20160919 (release) [ARM/embedded-5-branch revision 240496]"

Puisque `-Ofast 'est spécifié dans l'option d'optimisation, on suppose que le code qui met l'accent sur la vitesse d'exécution est craché, mais après tout cela est dû au compilateur moderne, il y a donc une partie inutile. L'une des théories de l'accélération est de "se concentrer sur l'intérieur de la boucle". Ce programme C a une structure à triple boucle, mais la partie de boucle la plus interne

python


            int p = u, q = v, i = 0;
            do {
                int r = p / 64; p = r * r; if (p < 0) goto l40;
                int s = q / 64; q = s * s; if (q < 0) goto l40;
                if (p + q < 0 || p + q > b) goto l40;
                p = p - q + u;
                q = 2 * r * s + v;
            } while (++i < n);

Je n'envisagerai d'accélérer que pour. La partie de boucle la plus interne est la source d'assembleur supérieure

python


	movs	r4, #32
	orrs	r2, r3
	mov	r3, r8
	str	r2, [sp, #4]
	str	r3, [sp]
	b	.L4
.L2:
	subs	r1, r1, r2
	ldr	r2, [sp]
	lsls	r0, r0, #1
	mov	r8, r2
	muls	r3, r0
	subs	r4, r4, #1
	adds	r1, r6, r1
	add	r3, r3, r8
	cmp	r4, #0
	beq	.L11
.L4:
	asrs	r0, r1, #31
	asrs	r2, r3, #31
	ands	r0, r7
	ands	r2, r7
	adds	r1, r0, r1
	adds	r3, r2, r3
	asrs	r0, r1, #6
	asrs	r3, r3, #6
	movs	r1, r0
	movs	r2, r3
	muls	r1, r0
	muls	r2, r3
	adds	r5, r1, r2
	cmp	r5, r10
	ble	.L2

Il a été converti en code. Dans la source C, le processus de sortie de la boucle avec goto lorsque les variables p et q deviennent négatives était une contre-mesure contre le débordement de multiplication qui peut se produire car la taille de la variable est de 16 bits dans le programme BASIC d'origine. est. Lors du portage vers C, la taille de la variable est de 32 bits et le jugement de débordement ne devrait pas être logiquement nécessaire, mais il est incorporé dans le but de porter fidèlement à partir du programme BASIC. J'ai été surpris que le code de sortie du compilateur ci-dessus ait cette partie supprimée proprement par optimisation. [Ajouté 2017.9.3] Le langage C ne considère pas le débordement d'entiers signés, donc le code ci-dessous

                int r = p / 64; p = r * r; if (p < 0) goto l40;
                int s = q / 64; q = s * s; if (q < 0) goto l40;

Les résultats négatifs de r et s au carré n'ont pas été pris en compte, et il semble que l'optimisation les a supprimés en tant que "traitement inutile".

D'ailleurs, en un coup d'œil

J'étais préoccupé par les points ci-dessus. La partie précédente

python


	movs	r4, #32
	orrs	r2, r3
	mov	r3, r8
	str	r2, [sp, #4]
	str	r3, [sp]
	movs	r7, r3
.L4:
	asrs	r0, r1, #6
	asrs	r2, r3, #6
	lsrs	r0, r0, #26
	lsrs	r2, r2, #26
	adds	r1, r0, r1
	adds	r3, r2, r3
	asrs	r1, r1, #6
	asrs	r3, r3, #6
	movs	r0, r1
	muls	r0, r3
	muls	r1, r1
	muls	r3, r3
	adds	r5, r1, r3
	cmp	r5, r10
	bgt	1f
	subs	r1, r1, r3
	lsls	r3, r0, #1
	adds	r1, r6, r1
	adds	r3, r7
	subs	r4, r4, #1
	bne	.L4
	b	.L11
1:

En changeant en, ces points seront résolus. Ce qui suit est une version de mandelbrot.s qui a été construite avec cette modification et convertie en programme BASIC.

text:mandelbrot.gcc.opt.bas


1 ' Mandelbrot set(hand optimized)
2 Z=#C03:Y=#700
3 X=0
4 IFPEEK(Z)=39X=X-1:W=33-PEEK(Z-X):IFW<1V=V<<7-W+W/96*34:POKEY,V>>(X&7):Y=Y+(X&7<7):GOTO4
5 Z=Z+PEEK(Z-1)+4:IFPEEK(Z-3)+PEEK(Z-2)GOTO3
6 CLS:CLT:FORI=1TO100:Z=USR(#700,0):NEXT:LC0,23:?TICK()
10 'Sa NL Ki Sz-eD4I ヲ cP, Um2a#=?"&j"%^y%Chi Hn2aj)%c&n-ji 9bW")q!J;M!$ja%Z{;ACNrcA-r-#n7 ・';3Ig#GmUEr-{DH)a*-Ga"ゥ g1%f-"23 js#Cga)jUQ'ヲ
11 'D'B#"1UdBra:1#G@!C#:qg!/j$i2gmR*)ゥ ・ 1a"QdEq YO U?UQvCb|mi'!Yg<Ri%Shuh=!%ー ugm2mUQj;Y!EZ ・<geNry$S!x\sensationnel%JC9,<C2!497GQ6;1"!0Q
12 '?P3%Ui.CDK&5g!U U$*-4YY}Alors#9"«Atsu».Echi

Le résultat de cela est mandelbrot.gcc.opt.jpg Il est devenu 253. (253-9) / 100 = 2,44 est le temps approximatif par session. Ce qui était de 2,84 avant l'optimisation manuelle est maintenant de 2,44, ce qui est environ 16,4% plus rapide. Je pense que l'effet était excellent pour l'optimisation effectuée dans une plage limitée.

Essayez d'apporter de petits changements à la logique

Dans le programme C précédent, l'adresse de la VRAM est calculée pour chaque point dessiné. Il y a quatre points dans une adresse de VRAM, mais il est inutile de calculer la même adresse quatre fois, alors pensons à l'amélioration. Ce point doit être atténué en effectuant successivement les calculs de quatre points existant à la même adresse VRAM. En tirant parti du fait que les adresses VRAM sont contiguës, le calcul d'adresse VRAM lui-même peut être omis. Ce qui suit est une application de ceci au programme C.

mandelbrot.c


#include <stdint.h>

int mandelbrot(int16_t arg, uint8_t* ram, const uint8_t* font)
{
    (void)arg; (void)font;
    int n = 32;
    int w = 64, h = 48;
    int a = 2 << 6, b = a * a, c = (3 << 12) / w;
    uint8_t* z = ram + 0x900;
    int v = (-h / 2) * c;
    for (int y = 0; y < h; y += 2) {
        int u = -((w * 3) / 4) * c;
        for (int x = 0; x < w; x += 2) {
            #define mandelbrot2(u, v) ({ \
                int p = u; \
                int q = v; \
                int a = 0; \
                int i = 0; \
                do { \
                    int r = p / 64; p = r * r; \
                    int s = q / 64; q = s * s; \
                    if (p + q < 0 || p + q > b) { \
                        a = 1; \
                        break; \
                    } \
                    p = p - q + u; \
                    q = 2 * r * s + v; \
                } while (++i < n); \
                (a); \
            })
            int k = 0x80;
            if (mandelbrot2(u, v)) {
                k += 1;
            }
            v += c;
            if (mandelbrot2(u, v)) {
                k += 4;
            }
            u += c;
            if (mandelbrot2(u, v)) {
                k += 8;
            }
            v -= c;
            if (mandelbrot2(u, v)) {
                k += 2;
            }
            *z++ = k;
            u += c;
        }
        v += 2 * c;
    }
    return 0;
}

L'adresse VRAM est définie en premier et n'est incrémentée que tous les 4 points écrits. Ce qui suit est une compilation de ceci avec gcc et un programme de BASIC.

mandelbrot.gcc.2.bas


1 ' Mandelbrot set(gcc)
2 Z=#C03:Y=#700
3 X=0
4 IFPEEK(Z)=39X=X-1:W=33-PEEK(Z-X):IFW<1V=V<<7-W+W/96*34:POKEY,V>>(X&7):Y=Y+(X&7<7):GOTO4
5 Z=Z+PEEK(Z-1)+4:IFPEEK(Z-3)+PEEK(Z-2)GOTO3
6 CLS:CLT:FORI=1TO100:Z=USR(#700,0):NEXT:LC0,23:?TICK()
10 'i)¡,KV-eD8 ㆍ S9E"&a3eXc 㽦 CA!-ngl@j45W!glCH)"--Ao!EUR!apDM)"p"3!Fq 㽹 Bs*)"=&a.y ァ!I!UU(iOCAED>q}ea 㽬 g22-C/!'a%Oh.Ud{g65U!l ー
11 '!JE.?ケ#DD2x5U,-ヲ D-A#"!0 "`a0U=EU"=CD7;1!I+U:E_OCA*O(Sa G9 "eTR"*ァ!;!?IVs/;9v2q#PU$KC\qB/UgO2'C"!fa<㽩;9 o!,!!Chi#!!dB / DA*Ensemble 3:
12 'Rr!#Ka 4Bi,C o#!eaAg;ReAD<)aA"q"#dN1 erfN.!3 s A;{8l~%)Uu U.qC)%E{9 o!z2j ァ V-8!"Alors%!"'dega4 su F*<-a&Ka Wd18 ー&ァ%"*!b-U ヲ)agW2!a$A#&'{
13 'B'D+{;!Fcaa VUP8[#2IS;Ugo 2)EQ#X"<'o!*m"4 ")Tuka Q$?B!;'(=$<1-":?!g|!H)ャ"0ceNaT5I$Ao moustique un%!>un shi]i2t5V*.LD]8|+:Mg A Z#;:.ZDG)ゥ p>Mg
14 'service xZ%Q$~ツ ツ a<p

Le résultat de l'exécution est mandelbrot.gcc.2.jpg (269-9) / 100 = 2,60, ce qui représente une amélioration d'environ 9% par rapport à 2,84 avant de changer la logique. Ce n'est pas aussi bon que l'optimisation manuelle ci-dessus, mais je pense que c'est un petit effet en tant que méthode simple. Au fait, si vous construisez avec clang, le programme

mandelbrot.clang.2.bas


1 ' Mandelbrot set(clang)
2 Z=#C03:Y=#700
3 X=0
4 IFPEEK(Z)=39X=X-1:W=33-PEEK(Z-X):IFW<1V=V<<7-W+W/96*34:POKEY,V>>(X&7):Y=Y+(X&7<7):GOTO4
5 Z=Z+PEEK(Z-1)+4:IFPEEK(Z-3)+PEEK(Z-2)GOTO3
6 CLS:CLT:FORI=1TO100:Z=USR(#700,0):NEXT:LC0,23:?TICK()
10 'Sa NR\!Ea!"#$!"&Mk"EA*)CFa:"O FCI$1"GAj)7A"1A!Qua&>%GB:1z.i,"""a・#2&2kE@aP!(-$)Aemg\1"e%g+p a)Aw"49a'q, kR'"!#45),{=a)ァ#B 0W!
11 '3cTA]Régler A8a$c"e%C+D5i`!¡!/9'1b*"-aB, d!u chiso c&W":IS3.4)W!!0:D'.r,!|Ea]!B!'d%ゥ 9$!!m'i!Différent "]ツ A8a$c"e1CCD5i`a¡!/='1r*B-aB, ヲ!u ゥ?c
12 '&W!KiQ3.:IWa!0:Dg/,,!~Ea]!b!'d"ァ "#Q;[)n[#Un service!Yq9c%EuTV(q,"",!・!2"2kE;eS$4ツ"1uアSc&ケ!ElER..9)U!!2:B'/,,!~Aka 18!%A"ゥ A597Q"
13 'Y#TN1*:eh)"・!s!-;=Pipi!:"2a*mEY"m)'$"E"{L Shi "#!O, U.)"(\ツ ツ!X@Tasso d Tata`aA!!!

Et le résultat de l'exécution est mandelbrot.clang.2.jpg Le résultat était (294-9) / 100 = 2,85. C'est une amélioration par rapport à l'exemple précédent 2.98 en clang, mais c'est une amélioration d'environ 4%, ce qui n'est pas aussi important que dans le cas de gcc.

assembleur

J'ai écrit le programme avec l'assembleur en me basant sur l'expérience de l'optimisation manuelle et du changement de logique.

mandelbrot.s


	.syntax unified
	.cpu cortex-m0
	.thumb

	.macro	mandelbrot2 bit
	mov	r2, r8
	mov	r3, r9
	movs	r4, #n
0:
	asrs	r0, r2, #6
	lsrs	r0, r0, #26
	adds	r2, r2, r0
	asrs	r2, r2, #6
	asrs	r0, r3, #6
	lsrs	r0, r0, #26
	adds	r3, r3, r0
	asrs	r3, r3, #6
	movs	r1, r2
	muls	r1, r3
	muls	r2, r2
	muls	r3, r3
	adds	r0, r2, r3
	cmp	r0, r10
	bgt	9f
	subs	r2, r2, r3
	add	r2, r8
	lsls	r3, r1, #1
	add	r3, r9
	subs	r4, #1
	bne	0b
	subs	r5, #\bit
9:
	.endm

	.text
	.align	1
	.global	mandelbrot
mandelbrot:
	.equiv	vramoffset, 0x900
	.equiv	n, 32
	.equiv	w, 64
	.equiv	h, 48
	.equiv	a, 2<<6
	.equiv	b, a*a
	.equiv	c, (3<<12)/w
	push	{r4, r5, r6, r7, lr}
	mov	r3, r8
	mov	r4, r9
	mov	r5, r10
	mov	r6, r11
	mov	r7, r12
	push	{r3, r4, r5, r6, r7}

	movs	r0, #vramoffset>>4
	lsls	r0, r0, #4
	adds	r6, r0, r1

	movs	r0, #c
	mov	r12, r0
	rsbs	r0, #0
	mov	r14, r0

	movs	r0, #b>>12
	lsls	r0, r0, #12
	mov	r10, r0

	ldr	r7, uend
	ldr	r0, vend
	mov	r11, r0

	ldr	r0, vstart
	mov	r9, r0
yloop:
	ldr	r0, ustart
	mov	r8, r0
xloop:
	movs	r5, #0x8f
	mandelbrot2 1
	add	r9, r12
	mandelbrot2 4
	add	r8, r12
	mandelbrot2 8
	add	r9, r14
	mandelbrot2 2
	add	r8, r12

	strb	r5, [r6]
	adds	r6, #1
	cmp	r7, r8
	bne	xloop

	add	r9, r12
	add	r9, r12
	cmp	r11, r9
	bne	yloop

	pop	{r3, r4, r5, r6, r7}
	mov	r8, r3
	mov	r9, r4
	mov	r10, r5
	mov	r11, r6
	mov	r12, r7
	movs	r0, #0
	pop	{r4, r5, r6, r7, pc}

	.align	2
vstart:
	.word	(-h/2)*c
vend:
	.word	(h/2)*c
ustart:
	.word	-((w*3)/4)*c
uend:
	.word	((w*(4-3))/4)*c

	.end

Comme résultat d'essayer d'utiliser au mieux les registres, il n'est plus nécessaire de placer les données dans les cadres de pile existants dans le code de sortie du compilateur C. Ce qui suit est un programme BASIC.

mandelbrot.asm.1.bas


1 ' Mandelbrot set(asm)
2 Z=#C03:Y=#700
3 X=0
4 IFPEEK(Z)=39X=X-1:W=33-PEEK(Z-X):IFW<1V=V<<7-W+W/96*34:POKEY,V>>(X&7):Y=Y+(X&7<7):GOTO4
5 Z=Z+PEEK(Z-1)+4:IFPEEK(Z-3)+PEEK(Z-2)GOTO3
6 CLS:CLT:FORI=1TO100:Z=USR(#700,0):NEXT:LC0,23:?TICK()
10 'Sa NIUSR-vD8i[<E Ui)!!+9Ra1B)ァ#+-g#)!!=*-dHq*);9 Clé Soiarh".03qi ァ{9aEi%Q!W iR3)ゥ#9!YW9neC1#je'sBw^!, Un F$X;Bs*)l!3*Ka vr!p]5C*-l
11 'D)%j!g!/*'3B-"RÉ!('ヲ*yeC!Mq E;-(q-5)qW Ka E;B2*Q#N)"?[{1B co aeB2jUR!j1)"", 1 ・ E2m%Q!Wu R<)eA&k.EdNq"cb+'UdE327!Fr!4 LC)?Donc Ie3:7g
12 '1*3"-!=3-Ec:ag!/.g4R)e!zBuiV{0A9I2A-iUcC3 "%{1#]UAD")VY!g[7Fr ur=5GN,-Ochi 8jc;cgv2wUV-!1]8}#9"«Atsu»"1!!!UNE`Tuu!Q!!

Le résultat de l'exécution est mandelbrot.asm.1.jpg (215-9) / 100 = 2,06, ce qui est le résultat le plus rapide à ce jour.

Résumé

Le tableau ci-dessous montre les résultats ci-dessus.

Système de traitement programme temps de traitement(L'unité est d'environ 1/60 secondes) temps de traitement最短のものと比べての倍率
IchigoJam BASIC original 31008 15052.43
gcc 5.4.1 Port fidèle du programme BASIC 2.84 1.38
clang 3.9.1 Port fidèle du programme BASIC 2.98 1.45
gcc 5.4.1 +Optimisation manuelle Port fidèle du programme BASIC 2.44 1.18
gcc 5.4.1 Modifications mineures de la logique 2.60 1.26
clang 3.9.1 Modifications mineures de la logique 2.85 1.38
GNU Binutils 2.26.2 2.06 1.00

Résumé,

C'est tout. En pensant à améliorer la vitesse d'exécution du programme

Etc. Cette fois, c'est un jeu complet, donc le coût est hors de considération. Si vous écrivez le programme dans un langage de haut niveau tel que C, il est facile de concevoir la logique pour accélérer, et inversement, une fois que vous l'écrivez dans l'assembleur, il sera difficile de changer la logique, donc tout d'abord, C etc. Ecrire dans un langage de haut niveau et concevoir la logique semble être un moyen efficace d'optimisation. Le code de sortie gcc que j'ai vu cette fois était clairement inutile. Je voudrais m'attendre à de nouvelles améliorations dans gcc et clang.

J'ai proposé une accélération supplémentaire, alors je l'ai ajoutée

À propos de la division par 64 où les variables r et s sont calculées dans le programme C

python


                int r = p / 64;
                int s = q / 64;

Dans le code de sortie gcc

python


.L4:
    asrs    r0, r1, #31
    asrs    r2, r3, #31
    ands    r0, r7
    ands    r2, r7
    adds    r1, r0, r1
    adds    r3, r2, r3
    asrs    r0, r1, #6
    asrs    r3, r3, #6

Les bits de signe des variables p et q sont étendus à 32 bits, masqués avec la valeur de r7 (63), ajoutés à la valeur d'origine, et arithmétique 6 bits décalés vers la droite. Même dans la version assembleur du programme, l'utilisation des registres est différente, mais l'idée est la même.

python


0:
    asrs    r0, r2, #6
    lsrs    r0, r0, #26
    adds    r2, r2, r0
    asrs    r2, r2, #6
    asrs    r0, r3, #6
    lsrs    r0, r0, #26
    adds    r3, r3, r0
    asrs    r3, r3, #6

Il s'agit d'une correction à une valeur négative qui est amenée à converger vers 0, que le dividende soit positif ou négatif lorsque la valeur signée est divisée par la valeur de puissance de 2. Arithmétique simple Lorsque vous utilisez uniquement le décalage à droite, les valeurs positives convergent vers 0, mais les valeurs négatives convergent vers -1, nous prenons donc des mesures contre cela. Dans ce programme C, la taille de la variable a été modifiée de 16 bits à 32 bits par rapport au programme BASIC d'origine, et comme il existe une marge de précision, la plage de valeurs pouvant être prises pour les variables p et q dans le programme est de 15 bits du côté MSB. Ont toujours la même valeur. Si vous l'utilisez, la partie au-dessus du programme de l'assembleur précédent sera

python


0:
    lsrs    r0, r2, #26
    adds    r2, r2, r0
    asrs    r2, r2, #6
    lsrs    r0, r3, #26
    adds    r3, r3, r0
    asrs    r3, r3, #6

Il est possible d'écrire. Voici le résultat de cette modification et de sa conversion en programme BASIC.

text:mandelbrot.asm.2.bas


1 ' Mandelbrot set(asm)
2 Z=#C03:Y=#700
3 X=0
4 IFPEEK(Z)=39X=X-1:W=33-PEEK(Z-X):IFW<1V=V<<7-W+W/96*34:POKEY,V>>(X&7):Y=Y+(X&7<7):GOTO4
5 Z=Z+PEEK(Z-1)+4:IFPEEK(Z-3)+PEEK(Z-2)GOTO3
6 CLS:CLT:FORI=1TO100:Z=USR(#700,0):NEXT:LC0,23:?TICK()
10 'Sa NIUSR-vD8i[<E Ui)!!+9Ra1B)ァ#+-g#)!!=*-`Hp*);9 clés iarhUe".03qi ァ{9aEi$cBeiD9('ヲ*yeC!Mq E;-(q-5)qW Ka E;B2*Q#N)"?\;1*Co ceB2jUR!j1
11 '(%d*1gQ/.g4R)e!zBuiV{0A9I2A-iUcC3 "%{1#]Annonce U")cD3"3A/*'3B-"=<-G ・ 2)"SdJ1 YO U?aQqCb|n1%eFa*UA%Shoc("+2%gFre#Ea=3-Ec:aYW9
12 'neC1#je'sBw^!, Un F$X;Bs*)l!3*Ka zr"0]%Bv ""<2 ozwhce ur:Service UMtD. G)Yu+;ugzrxeQ!b u. u.)"(\ツ ツ!%a!!$Zツ ツa"a!!

Faire cela a donné (198-9) / 100 = 1,89 (capture d'image omise). Voici une version mise à jour du tableau récapitulatif.

Système de traitement programme temps de traitement(L'unité est d'environ 1/60 secondes) temps de traitement最短のものと比べての倍率
IchigoJam BASIC original 31008 16406.35
gcc 5.4.1 Port fidèle du programme BASIC 2.84 1.50
clang 3.9.1 Port fidèle du programme BASIC 2.98 1.58
gcc 5.4.1 +Optimisation manuelle Port fidèle du programme BASIC 2.44 1.29
gcc 5.4.1 Modifications mineures de la logique 2.60 1.38
clang 3.9.1 Modifications mineures de la logique 2.85 1.51
GNU Binutils 2.26.2 2.06 1.09
GNU Binutils 2.26.2 Accélérer la division 1.89 1.00

Recommended Posts

La comparaison et l'optimisation des vitesses BASIC et C et assembleur jouent avec IchigoJam
Capture d'image / comparaison de la vitesse OpenCV avec et sans GPU
Comparaison de vitesse de Python, Java, C ++
Comparaison de vitesse du traitement de texte intégral de Wiktionary avec F # et Python
Comparaison de la grammaire de base entre Java et Python
Jouez avec la série Poancare et SymPy
Authentification de base, authentification Digest avec Flask
Accélérer la compilation C / C ++ avec ccache
Assembleur X86 sous Linux (lien avec C)
Fractal pour faire et jouer avec Python
Apprendre Python! Comparaison avec Java (fonction de base)
Jouez avec PDBBind de MoleculeNet et RDKitGridFeaturizer de DeepChem
Chargez CSV avec des pandas et jouez avec Index
RaspberryPi L Chika avec Python et C #
Comparaison de vitesse de murmurhash3, md5 et sha1