Optimal Base-10只有itoa()函数?

时间:2014-02-01 18:25:01

标签: c++ c performance string-formatting

在C语言编程的20多年里,我曾经使用过10次以外的基数,所以当我在另一个环境中发现我信任的MSVC的_itoa()缺失时,我开始写一个只有10的基数,然后把目标缓冲区参数,指向函数返回的存储,在左侧而不是右侧,与C标准库中的所有字符串函数一样。我相信这段代码也是线程安全的。

有更快的方法吗?

我还要询问正确性,但我相信包含的测试代码证明它有效,即使对于LONG_MIN的特定情况,即(-1 * LONG_MAX)-1,这导致代码失败,直到我改变了策略,注意了符号,然后将signed int复制到unsigned int。然后,我在函数中完成了无符号整数的所有核心工作 - 在75%的时间里也很愉快地运行。

char * _i32toa(char *const rtn, int32_t i)    {
    if (NULL == rtn) return NULL;

    // declare local buffer, and write to it back-to-front
    char buff[12];
    uint32_t  ut, ui;
    char minus_sign=0;
    char *p = buff + sizeof(buff)-1;
    *p-- = 0;    // nul-terminate buffer

    // deal with negative numbers while using an unsigned integer
    if (i < 0)    {
        minus_sign = '-';
        ui = (uint32_t)((int)-1 * (int)i);
    }    else    {
        ui = i;
    }

    // core code here...
    while (ui > 9) {
        ut = ui;
        ui /= 10;
        *p-- = (ut - (ui * 10)) + '0';
    }
    *p = ui + '0';

    if ('-' == minus_sign) *--p = minus_sign;

    // knowing how much storage we needed, copy chars from buff to rtn...
    memcpy(rtn, p, sizeof(buff)-(p - buff));

    return rtn;
}

// ------------------------------------------------------------------------------------------------
#define LOOP_KNT (SHRT_MAX * 1024)
// ------------------------------------------------------------------------------------------------
int main(void)    {
    time_t start = clock();

    int32_t t = 123456, i;
    char *buff = (char *)malloc(256);

    for (i = (SHRT_MIN *1024); i < LOOP_KNT; i++)    {
        _i32toa(buff, i);
    }
    printf("\nElapsed time was %f milliseconds", (double)clock() - (double)(start));

    start = clock();
    for (i = (SHRT_MIN * 1024); i < LOOP_KNT; i++)    {
        _itoa(i, buff, 10);
    }
    printf("\nElapsed time was %f milliseconds", (double)clock() - (double)(start));

    start = clock();
    for (i = (SHRT_MIN * 1024); i < LOOP_KNT; i++)    {
        ___itoa(i, buff, 10);
    }
    printf("\nElapsed time was %f milliseconds", (double)clock() - (double)(start));

    printf("\nString from integer %i is %s\n", t, _i32toa(buff, t));
    printf("\nString from integer %i is %s\n", -0, _i32toa(buff, -0));
    printf("\nString from integer %i is %s\n", -1, _i32toa(buff, -1));
    printf("\nString from integer %i is %s\n", LONG_MIN, _i32toa(buff, LONG_MIN));

    start = clock();
    for (int i = LONG_MIN; i < LONG_MAX; i++) {
        if (i != atoi(_i32toa(buff, (int32_t)i))) {
            printf("\nError for %i", i);
        }
        if (!i) printf("\nAt zero");
    }
    printf("\nElapsed time was %f milliseconds", (double)clock() - (double)(start));

    getchar();
    return 0;
}

性能是Visual Studio 2013中不属于C标准_itoa()的2-4倍,是sprintf()的10-15倍。

这种方法有点新颖,并且取决于知道完成的字符串所需的缓冲区大小 - 函数分配它自己的字符串缓冲区,buff []解决了这个问题,同时使它成为线程安全的。

知道缓冲区的结尾允许字符串的字符从后面写到前面,解决了逆序问题。调用函数不需要以任何方式准备* rtn,因为获取memcpy()ed到* ptr的工作字符串已经以空值终止。

TVMIA提供反馈。缺乏良好的_atoi()函数是一个持久的问题,值得一个好的解决方案。我们来做一个。

PS:在运行MSVS C ++ 64位且完全优化的i7 Hazwell盒子上,从LONG_MIN到LONG_MAX的完整循环平均每次转换116个时钟,用于往返,仅28个时钟for _itoa()。如果与Ben Voigt的代码相比,这超过了每秒725兆字节的字符串。我想我赢了Ben!

2 个答案:

答案 0 :(得分:3)

您可以直接写入来电者的记忆区域来消除memcpy 您应该让调用者传递缓冲区的大小。

另一个瓶颈是分裂,但我不知道如何解决这个问题。

编辑1:正确初始化缓冲区指针

char * _i32toa(char *const rtn, unsigned int buff_size, int32_t i)  
{
    if (NULL == rtn) return NULL;

    uint32_t  ut, ui;
    char minus_sign=0;
    char *p = rtn + buff_size - 1;
    // As before, without memcpy.
    return rtn;
}

答案 1 :(得分:1)

摆脱自动字符数组并使它们传递大小,以便检查溢出。

#define I32TOA( buff, val ) _i32toa( (buff), sizeof(buff), (val) )

char * _i32toa(char *const rtn, size_t size, int32_t i)    {
    if (NULL == rtn) return NULL;

    uint32_t  ut, ui;
    char minus_sign=0;
    char *p = rtn + size-1;
    *p-- = 0;    // nul-terminate buffer
    assert( p >= rtn );

    if (i < 0)    {
        minus_sign = '-';
        ui = (uint32_t)((int)-1 * (int)i);
    }    else    {
        ui = i;
    }

    while (ui > 9) {
        ut = ui;
        ui /= 10;
        *p-- = (ut - (ui * 10)) + 48;
        assert( p >= rtn );
    }
    *p = ui + 48;

    if ('-' == minus_sign) {
        *--p = minus_sign;
        assert( p >= rtn );
    }

    return p;
}