C 基准功能

C 基准功能,c,microcontroller,avr,C,Microcontroller,Avr,我已经编写了C代码——如下所示——来对一些函数进行基准测试。这个基准测试的主要目的是在Tiny85的AVR上测试这些功能,也可以在PC上测试(目前我使用的是atmega168而不是Tiny85,但基本相同) 该基准测试为它必须测试的每个函数以及接收待测试函数的相同参数但仅执行返回的“void”函数执行大量循环。在每个函数的循环结束时,它会写入一个标签和一个用usec表示的时间。该时间是由标签指定的函数的循环持续时间 我可能认为,如果我从要测试的函数的基准时间中减去“void”函数的基准时间,然后

我已经编写了C代码——如下所示——来对一些函数进行基准测试。这个基准测试的主要目的是在Tiny85的AVR上测试这些功能,也可以在PC上测试(目前我使用的是atmega168而不是Tiny85,但基本相同)

该基准测试为它必须测试的每个函数以及接收待测试函数的相同参数但仅执行返回的“void”函数执行大量循环。在每个函数的循环结束时,它会写入一个标签和一个用usec表示的时间。该时间是由标签指定的函数的循环持续时间

我可能认为,如果我从要测试的函数的基准时间中减去“void”函数的基准时间,然后将结果除以循环数,那么我就有了关于要测试的函数持续时间的足够信息。但事实并非如此,因为中断(甚至只是测量时间)

在任何情况下,我认为这个基准测试都能为我指明最快的函数。你怎么看?你有什么建议吗

下面是一个输出示例:

void 2110168
norm 2121500
base 2337196
basl 2450964
basw 2333980
ant4 2235236
ant5 2242904
unro 2270484
unrl 2590444
vect 2754188
vesw 2732472
标签和函数之间的链接可以通过查看基准代码中的查找表“static fnstest\u t fnt”看到

我在下面报告的代码可能是在PC上使用GCC 64位(32位,由于警告而做了一些修改)或在AVR上使用Arduino环境/AVR GCC编译的

下面是基准代码。代码中使用的类型测试在umul.h文件中被“typedefined”为uint16(此typedef的目的是方便地更改函数管理/返回的值的类型,但现在仅适用于其中的少数!)

#ifdef_uuavr__
#包括
#包括
#包括
#否则
#包括
#包括
#包括
#包括
#包括“timepn.h”
#恩迪夫
#包括“umul.h”
#ifndef未使用
#定义未使用(x)(无效)(x)
#恩迪夫
类型定义测试(测试a、测试b);
类型定义结构fntest{
fn_t*fn;
char*msg;
}fnut;
测试无效fn(测试a、测试b);
#ifndef\uuu AVR__
uint32_t micros();
#恩迪夫
静态fnt测试\u t fnt[]={
{nullfn,(char*)“void”},
{umul16_normal,(char*)“normal”},
{umul16_base,(char*)“base”},
{umul16_baseandlogic,(char*)“basl”},
{umul16_baseswap,(char*)“basw”},
{umul16_Antonio4,(char*)“ant4”},
{umul16_Antonio5,(char*)“ant5”},
{umul16_展开,(char*)“unro”},
{umul16_unrolled andlogic,(char*)“unrl”},
{umul16_vect,(char*)“vect”},
{umul16_vectswap,(char*)“vesw”}
};
#ifndef\uuu AVR__
uint32_t micros()
{
结构时间值t;
gettimeofday(&t,NULL);
返回(t.tv\U秒*1000000UL+t.tv\U usec);
}
#恩迪夫
测试无效Fn(测试a、测试b)
{
未使用的(a);未使用的(b);
返回0;
}
测试结果()
{
#ifdef__AVR__
#定义运行20000次
静态字符strbuf[50];
#否则
#定义运行10000000次
#恩迪夫
无符号整数i,j,k;
uint32_t x;
测试九,iy;
静态试验_t z[16];
对于(j=0;j>=1;
b0>>=1;
而(b0){///N个循环,最大为7
a+=a;
如果((b1和1))
res+=(测试)(uint8(a&0xff))*256;
如果((b0和1))
res+=a;
b1>>=1;
b0>>=1;//我尝试将使进位标志保持在所需状态的那个作为最后一个
}
uint8_t a0=a&0xff;//同样,不是真实副本,而是寄存器选择
而(b1){///P个循环,最大7-N个循环
a0+=a0;
如果((b1和1))
res+=(测试)a0*256;
b1>>=1;
}
返回res;
}
测试umul16测试基地(测试a、测试b)
{
测试结果=0;
而(二){
如果((b&1))
res+=a;
b> >=1;
a+=a;
}
返回res;
}
测试umul16基本和逻辑(测试a、测试b)
{
测试结果=0;
而(二){
//如果((b&1))
//res+=a;
res+=((0-!(!(b&1))&a);
b> >=1;
a+=a;
}
返回res;
}
测试umul16基本交换(测试a、测试b)
{
测试结果;
如果(a>=1;
a+=a;
}
返回res;
}
测试用例16安东尼奥4(测试a、测试b)
{
uint8_t res1=0;
uint8\u t a0=a&0xff;//这需要有效地复制数据
uint8\u t b0=b&0xff;//应该对此进行优化
uint8\u t b1=b>>8;//应该对此进行优化
//这里a0和b1可以互换(使b1>=1;
而(b1){///最多7个循环
a0+=a0;
如果((b1和1))
res1+=a0;
b1>>=1;
}
test\t res=(test\t)res1*256;//应该优化掉,它甚至不是一个副本!
//在这里,交换没有多大意义
如果((b0和1))
res+=a;
b0>>=1;
而(b0){///最多7个循环
a+=a;
如果((b0和1))
res+=a;
b0>>=1;
}
返回res;
}
测试工具(测试工具a、测试工具b)
{
测试c[2];
c[0]=0;c[1]=a;a=0;
而(二){
a+=c[(b&1)];
b> >=1;
c[1]+=c[1];
}
返回a;
}
测试umul16矢量WAP(测试a、测试b)
{
测试c[2];
如果(a>=1;
c[1]+=c[1];
}
返回a;
}
测试udiv(测试n、测试d、测试r)
{
测试q=0,i,r;
r=0;
if(d==0)返回(test_t)-1U;//错误

i=((test_t)(1)通常,为了消除中断的影响,您只需重复测试几次,并保持最快的响应作为测量

对于复杂的CPU,如x86,也需要重复,以消除对当前缓存内容和分支预测器统计信息的依赖


在现代CPU上,确保时钟是固定的也是非常重要的(大多数现代CPU在CPU大部分空闲时自动调节时钟以减少加热/消耗,时钟控制逻辑可能需要一些时间才能恢复全速).

我看到了一些改进基准代码的机会

首先,我将在开始时生成随机输入数据,并存储
#ifdef __AVR__
#include <Arduino.h>
#include <HardwareSerial.h>
#include <string.h>
#else
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/time.h>
#include "timefn.h"
#endif

#include "umul.h"

#ifndef UNUSED
#define UNUSED(x) (void)(x)
#endif

typedef test_t fn_t(test_t a,test_t b);

typedef struct fntest_s {
    fn_t * fn;
    char * msg;
} fntest_t;

test_t nullfn(test_t a,test_t b);
#ifndef __AVR__
uint32_t micros();
#endif

static fntest_t fnt[]={
    {nullfn,(char *)"void"},
    {umul16_normal,(char *)"normal"},
    {umul16_base,(char *)"base"},
    {umul16_baseandlogic,(char *)"basl"},
    {umul16_baseswap,(char *)"basw"},
    {umul16_Antonio4,(char *)"ant4"},
    {umul16_Antonio5,(char *)"ant5"},
    {umul16_unrolled,(char *)"unro"},
    {umul16_unrolledandlogic,(char *)"unrl"},
    {umul16_vect,(char *)"vect"},
    {umul16_vectswap,(char *)"vesw"}
};

#ifndef __AVR__
uint32_t micros()
{
    struct timeval t;
    gettimeofday(&t,NULL);

    return (t.tv_sec*1000000UL+t.tv_usec);
}
#endif

test_t nullfn(test_t a,test_t b)
{
    UNUSED(a);UNUSED(b);
    return 0;
}

test_t umultry()
{
#ifdef __AVR__
#define RUNS 20000
static char strbuf[50];
#else
#define RUNS 10000000
#endif

    unsigned int i,j,k;
    uint32_t x;

    test_t ix,iy;
    static test_t z[16];

    for(j=0;j<5;j++) {
        for(k=0;k<sizeof(fnt)/sizeof(fntest_t);k++) {
            x=micros();srand(x);
            for(i=0;i<RUNS;i++) {
                ix=rand();iy=rand();
                z[i&0xF]+=fnt[k].fn(ix,iy);
            }
            x=micros()-x;
#ifdef __AVR__
            sprintf(strbuf,"%s %lu\n",fnt[k].msg, x);
            Serial.print(strbuf);
#else
            printf("%s %u\n",fnt[k].msg, x);
#endif
        }
        for(i=0;i<16;i++) {
            z[0]+=z[i]; /* To avoid warn unused and the optimizations don't evaluate z[]*/
        }

#ifdef __AVR__
        Serial.println("----------------");
#else
        puts("----------------");
#endif
    }

    return z[0];
}

#ifdef __AVR__
void setup()
{
    Serial.begin(115200);
    Serial.println(F("Starting..."));
}

void loop()
{
    umultry();
    for(;;);
}
#else
int main(void)
{
    puts("Starting...");
    return umultry();
}
#endif
#include "umul.h"

test_t umul16_normal(test_t a, test_t b)
{
    return a*b;
}

test_t umul16_unrolled(test_t a, test_t b)
{
test_t result=0;

#define UMUL16_STEP(a, b, shift) \
    if ((b) & (1U << (shift))) result += (a<<shift);

    UMUL16_STEP(a, b, 0);
    UMUL16_STEP(a, b, 1);
    UMUL16_STEP(a, b, 2);
    UMUL16_STEP(a, b, 3);
    UMUL16_STEP(a, b, 4);
    UMUL16_STEP(a, b, 5);
    UMUL16_STEP(a, b, 6);
    UMUL16_STEP(a, b, 7);
    UMUL16_STEP(a, b, 8);
    UMUL16_STEP(a, b, 9);
    UMUL16_STEP(a, b, 10);
    UMUL16_STEP(a, b, 11);
    UMUL16_STEP(a, b, 12);
    UMUL16_STEP(a, b, 13);
    UMUL16_STEP(a, b, 14);
    UMUL16_STEP(a, b, 15);

    return result;
#undef UMUL16_STEP
}

test_t umul16_unrolledandlogic(test_t a, test_t b)
{
test_t result=0;
#define UMUL16_STEP(a, b, shift) \
/*    if ((b) & (1U << (shift))) result += (a<<shift);*/\
    result+=  ((0 - !(!((b&(1U<<(shift)))))) & (a<<(shift)));

    UMUL16_STEP(a, b, 0);
    UMUL16_STEP(a, b, 1);
    UMUL16_STEP(a, b, 2);
    UMUL16_STEP(a, b, 3);
    UMUL16_STEP(a, b, 4);
    UMUL16_STEP(a, b, 5);
    UMUL16_STEP(a, b, 6);
    UMUL16_STEP(a, b, 7);
    UMUL16_STEP(a, b, 8);
    UMUL16_STEP(a, b, 9);
    UMUL16_STEP(a, b, 10);
    UMUL16_STEP(a, b, 11);
    UMUL16_STEP(a, b, 12);
    UMUL16_STEP(a, b, 13);
    UMUL16_STEP(a, b, 14);
    UMUL16_STEP(a, b, 15);

    return result;
#undef UMUL16_STEP
}

test_t umul16_Antonio5(test_t a, test_t b)
{
    test_t res = 0;

    uint8_t b0 = b & 0xff; //This should be optimized away
    uint8_t b1 = b >>8; //This should be optimized away

    //Swapping probably doesn't make much sense anymore
    if ( (b1 & 1) )
        res+=(test_t)((uint8_t)(a && 0xff))*256;
    //Hopefully the compiler understands it has simply to add the low 8bit register of a to the high 8bit register of res

    if ( (b0 & 1) )
        res+=a;

    b1>>=1;
    b0>>=1;
    while (b0) {///N cycles, maximum 7
        a+=a;
        if ( (b1 & 1) )
            res+=(test_t)((uint8_t)(a & 0xff))*256;
        if ( (b0 & 1) )
            res+=a;
        b1>>=1;
        b0>>=1; //I try to put as last the one that will leave the carry flag in the desired state
    }

    uint8_t a0 = a & 0xff; //Again, not a real copy but a register selection

    while (b1) {///P cycles, maximum 7 - N cycles
        a0+=a0;
        if ( (b1 & 1) )
            res+=(test_t) a0 * 256;
        b1>>=1;
    }
    return res;
}

test_t umul16_base(test_t a, test_t b)
{
    test_t res=0;

    while (b) {
        if ( (b & 1) )
            res+=a;
        b>>=1;
        a+=a;
    }

    return res;
}

test_t umul16_baseandlogic(test_t a, test_t b)
{
    test_t res=0;

    while (b) {
        //if ( (b & 1) )
        //    res+=a;
        res+=  ((0 - !(!(b&1))) & a);
        b>>=1;
        a+=a;
    }

    return res;
}

test_t umul16_baseswap(test_t a, test_t b)
{
    test_t res;

    if (a<b) {
        res=a;
        a=b;
        b=res;
    }

    res=0;
    while (b) {
        if ( (b & 1) )
            res+=a;
        b>>=1;
        a+=a;
    }

    return res;
}

test_t umul16_Antonio4(test_t a, test_t b)
{
    uint8_t res1 = 0;

    uint8_t a0 = a & 0xff; //This effectively needs to copy the data
    uint8_t b0 = b & 0xff; //This should be optimized away
    uint8_t b1 = b >>8; //This should be optimized away

    //Here a0 and b1 could be swapped (to have b1 < a0)
    if ( (b1 & 1) )
        res1+=a0;
    b1>>=1;
    while (b1) {///Maximum 7 cycles
        a0+=a0;
        if ( (b1 & 1) )
            res1+=a0;
        b1>>=1;
    }

    test_t res = (test_t) res1 * 256; //Should be optimized away, it's not even a copy!

    //Here swapping wouldn't make much sense
    if ( (b0 & 1) )
        res+=a;
    b0>>=1;
    while (b0) {///Maximum 7 cycles
        a+=a;
        if ( (b0 & 1) )
            res+=a;
        b0>>=1;
    }

    return res;
}

test_t umul16_vect(test_t a, test_t b)
{
    test_t c[2];

    c[0]=0;c[1]=a;a=0;
    while (b) {
        a+=c[(b & 1)];
        b>>=1;
        c[1]+=c[1];
    }

    return a;
}

test_t umul16_vectswap(test_t a, test_t b)
{
    test_t c[2];

    if (a<b) {
        c[1]=b;
        b=a;
        a=c[1];
    }

    c[0]=0;c[1]=a;a=0;
    while (b) {
        a+=c[(b & 1)];
        b>>=1;
        c[1]+=c[1];
    }

    return a;
}

test_t udiv_(test_t n,test_t d, test_t *r)
{
    test_t q = 0,i,r_;

    r_=0;
    if (d == 0) return (test_t)-1U; //error

    i= ( (test_t)(1) << ((sizeof(n)<<3)-1) );
    for (;i!=0;i>>=1) {
        r_ <<= 1;

        if (n&i)
            r_ |= 1;

        if (r_ >= d) {
            r_ -= d;
            q |= i;
        }
    }
    if (r!=NULL)
        *r=r_;

    return q;
}
#ifndef __UMUL_H
#define __UMUL_H

#ifdef __AVR_ATtiny85__
typedef signed int int8_t __attribute__((__mode__(__QI__)));
typedef unsigned int uint8_t __attribute__((__mode__(__QI__)));
typedef signed int int16_t __attribute__ ((__mode__ (__HI__)));
typedef unsigned int uint16_t __attribute__ ((__mode__ (__HI__)));
typedef signed int int32_t __attribute__ ((__mode__ (__SI__)));
typedef unsigned int uint32_t __attribute__ ((__mode__ (__SI__)));
typedef signed int int64_t __attribute__((__mode__(__DI__)));
typedef unsigned int uint64_t __attribute__((__mode__(__DI__)));

#define NULL 0
#else
#include <stdlib.h>
#include <stdint.h>
#endif

typedef uint16_t test_t;

#ifdef __cplusplus
extern "C" {
#endif

test_t umul16_normal(test_t a, test_t b);
test_t umul16_unrolled(test_t a, test_t b);
test_t umul16_unrolledandlogic(test_t a, test_t b);
test_t umul16_Antonio5(test_t a, test_t b);
test_t umul16_base(test_t a, test_t b);
test_t umul16_baseswap(test_t a, test_t b);
test_t umul16_Antonio4(test_t a, test_t b);
test_t umul16_vect(test_t a, test_t b);
test_t umul16_vectswap(test_t a, test_t b);
test_t umul16_baseandlogic(test_t a, test_t b);
    test_t udiv_(test_t n,test_t d, test_t *r);
} // extern "C"
#endif

#endif
   for(i=0;i<16;i++) {
        z[i] = 0;
    }
   for(i=0;i<16;i++) {
        z[0]+=z[i]; /* To avoid warn unused and the optimizations don't evaluate z[]*/
    }
   for(i=1;i<16;i++) {
        z[0]+=z[i]; /* To avoid warn unused and the optimizations don't evaluate z[]*/
    }