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. 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
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. 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. 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 Puisqu'il est 9, (293-9) / 100 = 2,84 est le temps d'exécution approximatif par exécution.
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 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.
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 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.
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 (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 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.
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 (215-9) / 100 = 2,06, ce qui est le résultat le plus rapide à ce jour.
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.
À 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