Как да добавя забавяне от 150 цикъла на ARM?

Смяната на издърпващите резистори нагоре/надолу за GPIO пинове на Raspberry Pi изисква изчакване за 150 цикъла след активиране и дезактивиране на часовниковия сигнал според спецификациите. Да го правите малко по-дълго не боли, но използването на таймер за изчакване е по-дълго с величини, така че не искам да го правя. Така че имам този прост зает цикъл:

for (int i = 0; i < 150; ++i) { asm volatile (""); }

0:   e3a03096        mov     r3, #150        ; 0x96
4:   e2533001        subs    r3, r3, #1
8:   1afffffd        bne     4 <foo+0x4>

Това зацикля 150 пъти, изпълнявайки 300 инструкции. Без кеширане на инструкции и без предвиждане на разклонения това със сигурност е повече от 150 цикъла. Но след като те бъдат включени, този цикъл работи много по-бързо, по-бързо от 150 цикъла, мисля.

И така, как да изчакам близо 150 цикъла със или без кеширане на инструкции и активирано предвиждане на разклонения? Забележка: в най-лошия случай това може да са 2 функции, delay_no_cache() и delay_cache()

Това не е дубликат на Как да забавите ARM Cortex M0+ за n цикъла, без таймер? тъй като кешът на инструкциите и предвиждането на разклонения напълно хвърлят времето. Времето също се различава между Raspberry Pi (ARMv6) и Raspberry Pi2 (ARMv7).

Някой знае ли времето за изпълнение (със и без кеш), ако някой би вмъкнал DMB, DSB (предполагам, че това биха били NOP, тъй като няма достъп до ram) или ISB инструкция в цикъла? Това ще предотврати ли ефекта на бягство, когато кешовете са активирани?


person Goswin von Brederlow    schedule 07.04.2015    source източник
comment
възможен дубликат на Как да забавя ARM Cortex M0+ за n цикъла, без таймер?   -  person samgak    schedule 07.04.2015
comment
Не е дубликат. В идеалния случай търся цикъл, който има (почти) същото време на инструкции с и без кеширане на инструкции и предвиждане на разклонения. Знам, че простият цикъл по-горе работи около 500 пъти по-бързо с кеширане на инструкции и предвиждане на разклонения.   -  person Goswin von Brederlow    schedule 07.04.2015


Отговори (2)


Направих няколко измервания на моя Raspberry PI 2 работа delay() за 1-100000000 цикли в коефициенти 10 и изчислих броя на цикъла от изминалото време. Това показва, че без кешове само едно преминаване през празен цикъл е повече от достатъчно за забавяне от 150 цикъла (това е само sub + bcs). Единичен NOP (в последователност от 150) отнема 32 (общо 5030) цикъла без кеш памети и 1,5 цикъла с (общо 226,5). The orr;add;and;mov;orr;add;and;mov; цикъл също така показва колко конвейерна и суперскаларна е CPU, като отнема само 0,15 цикъла на код на операция. Не е толкова добре да се получи цикъл на добре време.

В заключение трябва да се откажа и просто да използвам забавяне, базирано на таймера. Това всъщност е по-бързо без кешове, отколкото цикъл, който отнема 150 цикъла с кешове.

void delay(uint32_t count) {
    uint32_t a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0;
    while(count--) {
// branch icache dcache cycles/loop
// no     no     no     ~507
// no     no     yes      43.005
// no     yes    no        1.005
// no     yes    yes       1.005
// yes    no     no     ~507
// yes    no     yes      43.005
// yes    yes    no        1.005
// yes    yes    yes       1.005
// asm ("");

// branch icache dcache cycles/loop
// no     no     no     ~750
// no     no     yes      67.500
// no     yes    no       16.500
// no     yes    yes      16.500
// yes    no     no     ~750
// yes    no     yes      67.500
// yes    yes    no       16.500
// yes    yes    yes      16.500
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");

// branch icache dcache cycles/loop
// no     no     no     ~505
// no     no     yes      43.500
// no     yes    no        1.500
// no     yes    yes       1.500
// yes    no     no     ~505
// yes    no     yes      43.500
// yes    yes    no        1.500
// yes    yes    yes       1.500
 asm ("orr %0, %0, %0" : "=r" (a) : "r" (a));
 asm ("add %0, %0, %0" : "=r" (b) : "r" (b));
 asm ("and %0, %0, %0" : "=r" (c) : "r" (c));
 asm ("mov %0, %0" : "=r" (d) : "r" (d));
 asm ("orr %0, %0, %0" : "=r" (e) : "r" (e));
 asm ("add %0, %0, %0" : "=r" (f) : "r" (f));
 asm ("and %0, %0, %0" : "=r" (g) : "r" (g));
 asm ("mov %0, %0" : "=r" (h) : "r" (h));

// branch icache dcache cycles/loop
// no     no     no     ~1010
// no     no     yes       85.005
// no     yes    no        18.000
// no     yes    yes       18.000
// yes    no     no     ~1010
// yes    no     yes       85.005
// yes    yes    no        18.000
// yes    yes    yes       18.000
// isb();

// branch icache dcache cycles/loop
// no     no     no     ~5075
// no     no     yes      481.501
// no     yes    no       141.000
// no     yes    yes      141.000
// yes    no     no     ~5075
// yes    no     yes      481.501
// yes    yes    no       141.000
// yes    yes    yes      141.000
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
    }
}
person Goswin von Brederlow    schedule 18.04.2015
comment
asm ("orr %0, %0, %0" : "=r" (a) : "r" (a)); не използва %1, така че може да използва какъвто и да е боклук в изходния операнд като вход, ако компилаторът избере различни входно/изходни регистри. Вече имате достатъчно паралелизъм на ниво инструкции, така че веригите на зависимости, пренасяни от цикъла, да не пречат на латентността, така че няма особено значение. (Скептично съм настроен към 0,15 цикъла на инструкция. Дори AMD Ryzen може да управлява само 5 инструкции на такт (или 6 uops, ако има multi-uop инструкции), а процесорите Intel x86 са 4-широки суперскаларни. 6,66 широк не е правдоподобен. - person Peter Cordes; 01.09.2017
comment
О, мисля, че знам какво се случи. не сте използвали asm volatile и вашата функция не използва a,b,c,d,e,f,g,h след цикъла, така че gcc оптимизира всичките ви asm изрази. (не-volatile asm с поне един изходен операнд се счита за чиста функция на своите входове без странични ефекти. gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Volatile). Да, godbolt.org/g/Qg7r8N потвърждава, че точно това се е случило, ако сте компилирали с -O1 или по-нова версия. Вашата функция дори компилира добре на x86, защото asm оптимизира далеч :P Винаги проверявайте asm изхода на компилатора. - person Peter Cordes; 01.09.2017

Може да се наложи да използвате ФУНКЦИЯ ЗА ПОВТОРЕНИЕ НА МАКРО, за да направите вашето забавяне. Използването на цикъл по време на изпълнение винаги ще има оптимизация, а самият цикъл също струва време. Можете да итерирате макрос на NOP 150 пъти. Няма да има оптимизация и излишни цикли.

Ето шаблон за повтарящ се макрос:

#define MACRO_CMB( A , B)           A##B
#define M_RPT(__N, __macro)         MACRO_CMB(M_RPT, __N)(__macro)

#define M_RPT0(__macro)
#define M_RPT1(__macro)             M_RPT0(__macro)   __macro(0)
#define M_RPT2(__macro)             M_RPT1(__macro)   __macro(1)
#define M_RPT3(__macro)             M_RPT2(__macro)   __macro(2)
...
#define M_RPT256(__macro)           M_RPT255(__macro) __macro(255)

Можете да дефинирате вашата NOP инструкция по следния начин:

#define MY_NOP(__N)                 __asm ("nop");    // or sth like "MOV R0,R0"

След това можете да повторите инструкцията 150 пъти, като просто извикате това:

M_RPT(150, MY_NOP);

Наистина ще бъде изпълнено 150 пъти.

Надявам се това да е помогнало.

person kevin wu    schedule 08.04.2015
comment
Това изобщо не помага. Проблемът не е оптимизация, тъй като asm() предотвратява това. Също така не е, че самата верига струва. Тази цена може да се вземе предвид при забавянето или да се минимизира чрез отвиване. Проблемът е, че 150 NOP отнемат 150 цикъла без кеш, но само 15 цикъла с (или нещо подобно). - person Goswin von Brederlow; 09.04.2015