https://hueffner.de/falk/blog/a-leap-year-check-in-three-instructions.html
bool is_leap_year_fast(uint32_t y) {
return ((y * 1073750999) & 3221352463) <= 126976;
}
https://hueffner.de/falk/blog/a-leap-year-check-in-three-instructions.html
bool is_leap_year_fast(uint32_t y) {
return ((y * 1073750999) & 3221352463) <= 126976;
}
То там и класическия алгоритъм няма да е бърз като няма инструкции за умножение и деление/остатък.
А умножение с константа може да се напише доста ефективно с бит шифт, та без проба трудно може да се прецени колко ще е бърз този алгоритъм на 8 битов процесор. Но на 32 битов процесор с хардуерно умножение е друга бира.
А за какво ти е да знаеш високосна година на 8051 ... ?
По принцип е забавно, но е типичен пример за безмислена оптимизация. Такъв код трябва да е четим, а не бърз.
Е, то без обясненията в линка кода изглежда като магия. Пък и автора си признава, че е правил brute force за да намери подходящи константи. Нещо подобно на обратния квадрат в quake.
Малко е тъпо да мериш без да гледаш какъв асемблерски код е генериран, но много подозирам, че причината първите имплементации са толкова бавни е, че има три return-а вместо един.
Това е кода, който генерира clang O3 на Мака от функцията is_leap_year1:
mov w8, #1 ; =0x1
mov w9, #23593 ; =0x5c29
movk w9, #49807, lsl #16
mul w9, w0, w9
mov w10, #28835 ; =0x70a3
movk w10, #2621, lsl #16
tst w0, #0xc
cset w11, eq
cmp w9, w10
csel w8, w8, w11, hi
tst w0, #0x3
csel w0, wzr, w8, ne
ret
Това е is_leap_year_fast:
mov w8, #9175 ; =0x23d7
movk w8, #16384, lsl #16
mul w8, w0, w8
mov w9, #61455 ; =0xf00f
movk w9, #49153, lsl #16
and w8, w8, w9
cmp w8, #31, lsl #12 ; =126976
cset w0, ls
ret
Изглеждат много подобни. Мързи ме да меря, но се съмнявам, че първия е по-бавен от втория, като се има предвид колко независими data paths има в първия и втория случай и че процесорите изпълняват доста от инструкциите едновременно.
Не знам за Интел, вече нямам машини с x86.
Та, извода е а) учете асемблер; б) не си губи времето с глупости, освен ако не е за забавление. :)
Бях забравил, че кланг сигурно може да компилира и за x86. Blah, the x86 code is NASTY и има 2 jump-a и два ret-a.
is_leap_year1()
# %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
xorl %eax, %eax
testb $3, %dil
je .LBB0_1
.LBB0_3:
popq %rbp
.cfi_def_cfa %rsp, 8
retq
.LBB0_1:
.cfi_def_cfa %rbp, 16
imull $-1030792151, %edi, %ecx # imm = 0xC28F5C29
movl $1, %eax
cmpl $171798691, %ecx # imm = 0xA3D70A3
ja .LBB0_3
# %bb.2:
xorl %eax, %eax
testb $12, %dil
sete %al
popq %rbp
.cfi_def_cfa %rsp, 8
retq
is_leap_year_fast()
# %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
imull $1073750999, %edi, %ecx # imm = 0x400023D7
andl $-1073614833, %ecx # imm = 0xC001F00F
xorl %eax, %eax
cmpl $126977, %ecx # imm = 0x1F001
setb %al
popq %rbp
.cfi_def_cfa %rsp, 8
retq
Фокуса ми е в красотата на неортодоксалния алгоритъм. Подхода за намирането му и т.н.
Няма смисъл да ми обясняваш, че няма смисъл да се оптимизира нещо което се ползва от дъжд на вятър.
Търсих една тема дето беше писал, че нямало вече програмистки разговори и като не я намерих отворих тази.
Ай наздраве ти! Изнамерих в хладилника една изостанала черешова бира и го пия с отвращение тоя добре ферментирал компот. 8 градуса. Би се ядвало ако не го водят бира.
Би ли пуснал в продукция код, който не разбираш, урсулопитек малоумен?
Интересно е, но най-изненадващото е, че алгоритъма очевидно не е толкова неортодоксален. Компилатора генерира много подобен код.
А което е още по-интересно е, че това явно го прави backend-a, защото IR няма никакви странни константи:
define range(i32 0, 2) i32 @is_leap_year1(i32 noundef %0) local_unnamed_addr #0 {
%2 = and i32 %0, 3
%3 = icmp eq i32 %2, 0
br i1 %3, label %4, label %11
4: ; preds = %1
%5 = urem i32 %0, 25
%6 = icmp eq i32 %5, 0
br i1 %6, label %7, label %11
7: ; preds = %4
%8 = and i32 %0, 12
%9 = icmp eq i32 %8, 0
%10 = zext i1 %9 to i32
br label %11
11: ; preds = %7, %4, %1
%12 = phi i32 [ 0, %1 ], [ 1, %4 ], [ %10, %7 ]
ret i32 %12
}
Това което ти се струва аналогично май всъщност е %25 изчислен с умножение вместо с деление. А в три операционния алгоритъм заигравката със сметките е по дълбоко. Иначе спор няма, че компилаторите захитряват все повече и повече.
СМЪРТ ЗА АМЕРИКА КУР ЗА АМЕРИКА ЧУМА ЗА АМЕРИКА АТОМ ЗА АМЕРИКА
СМЪРТ ЗА ИЗРАЕЛ КУР ЗА ИЗРАЕЛ ЧУМА ЗА ИЗРАЕЛ АТОМ ЗА ИЗРАЕЛ
СМЪРТ ЗА АНГЛИЯ КУР ЗА АНГЛИЯ ЧУМА ЗА АНГЛИЯ АТОМ ЗА АНГЛИЯ
СМЪРТ ЗA ЕВРЕОГЕЙСКИЯ СЪЮЗ КУР ЗА ЕВРЕОГЕЙСКИЯ СЪЮЗ ЧУМА ЗА ЕВРЕОГЕЙСКИЯ СЪЮЗ АТОМ ЗА ЕВРЕОГЕЙСКИЯ СЪЮЗ
СМЪРТ ЗА АМЕРИКАНСКИЯ БУМЕР КУР ЗА АМЕРИКАНСКИЯ БУМЕР ЧУМА ЗА АМЕРИКАНСКИЯ БУМЕР АТОМ ЗА АМЕРИКАНСКИЯ БУМЕР
СМЪРТ ЗА ЖИДОВЕТЕ КУР ЗА ЖИДОВЕТЕ ЧУМА ЗА ЖИДОВЕТЕ АТОМ ЗА ЖИДОВЕТЕ
СМЪРТ ЗА НАГЛО-САКСОНЦИТЕ КУР ЗА НАГЛО-САКСОНЦИТЕ ЧУМА ЗА НАГЛО-САКСОНЦИТЕ АТОМ ЗА НАГЛО-САКСОНЦИТЕ
Да не отварям нова тема:
Размисли относно паралелизиране при изчисляване на 256 битови числа на 64 битова архитектура.
в университета учихме техика за паралелен пренос, тя си се ползва в процесорите инзаче забрави за 32 битов суматор на 3 гигахерца
Интересна идея и е добро четиво за хора, на които не им е ясно как се изпълняват инструкциите в процесора.
Не знам доколко е практично в реален код обаче. Първо че увеличава броя използвани регистри, и второ предполага, че няма други аритметични операции, които да запълнят пайплаайна.
Да пусна и аз един линк…
Смятане на умножение на fp64 матрици с използване на int8 матрични операции: