【问题标题】:128 bit comparison under AIX (64 bits) for sorting hash valuesAIX 下的 128 位比较(64 位)用于对哈希值进行排序
【发布时间】:2011-11-02 06:33:28
【问题描述】:

我需要使用 xlC_r 编译器在 AIX(64 位)下对这样的结构数组进行排序:

struct digest_line {
    uint64_t first;
    uint64_t second;
};

现在我正在做很长的路(比较第一个元素,如果它们相等,则比较第二个元素。)有没有更快的方法来比较这些值?

编辑:我忘了提到我正在使用 AIX 的 qsort() 函数。根据qsort的man page,比较函数的定义如下

int  (*ComparisonPointer)(const void*, const void*);

这(对我来说)意味着我不能只返回一个 int64_t 值,而是这样的:

int compare_digests(const void *a, const void *b)
{
        struct digest_line *aa = (struct digest_line *) a;
        struct digest_line *bb = (struct digest_line *) b;

        int64_t ret = aa->first - bb->first;
        if (!ret) {
                ret = aa->second - bb->second;
        }
        return (ret == 0) ? 0 : (ret > 0) ? 1 : -1;
}

这看起来不... 。我一直在想一定有更好的方法。

【问题讨论】:

    标签: c aix


    【解决方案1】:

    可能在任何平台上最好的选择就是使用memcmp。这应该在任何体面的架构上进行高度优化(和内联)。查看汇编程序应该会告诉您编译器是否进行了一些巧妙的优化。然后基准测试可以告诉您哪个版本最好,因为例如对齐问题也可能起作用并且取决于您拥有的数据类型。

    我手头没有你的架构,所以我很快用 gcc 检查了我的旧 i686。以下函数的汇编器

    int compare(struct digest* a, struct digest* b) {
      return memcmp(a, b, sizeof *a);
    }
    

    经过优化后看起来相当不错。

    编辑: Jonathan 在他的评论中是正确的,这不一定给出 128 位模式的数字顺序。但只要您只对一致的顺序感兴趣,以便将顺序 :) 带入您的摘要,这应该在所有平台上都可以正常工作。 AFAIR AIX 平台是大端的,所以它应该在那里运行良好。

    【讨论】:

    • qsort() 比较函数需要提供排序,而不仅仅是“相等或不相等”结果。
    • 即使没有!!,这在小端平台或大端平台上都会失败(我认为是小端,比如英特尔),所以它肯定是不可移植的.
    • @Jonathan,我认为 OP 不需要数字排序。 AFAIKS 这是用于摘要,通常您唯一需要的是一致的东西。
    • 我需要一致的排序,以便在大型摘要文件之间执行类似 diff 的操作。 memcmp() 看起来是一种有效的方法。
    【解决方案2】:

    您的代码有问题,因为您正在对无符号数据进行有符号比较。使用以下替代方法之一:

    更正统

    这明显更快。

    int compare_digests(const void *a, const void *b)
    {
            const struct digest_line *aa = (const struct digest_line *) a;
            const struct digest_line *bb = (const struct digest_line *) b;
    
            if (aa->first > bb->first)
                return +1;
            else if (aa->first < bb->first)
                return -1;
            else if (aa->second > bb->second)
                return +1;
            else if (aa->second < bb->second)
                return -1;
            else
                return 0;
    }
    

    少正统

    这明显变慢了;不要使用它。

    int compare_digests(const void *a, const void *b)
    {
            struct digest_line aa = *(struct digest_line *) a;
            struct digest_line bb = *(struct digest_line *) b;
    
            if (aa.first > bb.first)
                return +1;
            else if (aa.first < bb.first)
                return -1;
            else if (aa.second > bb.second)
                return +1;
            else if (aa.second < bb.second)
                return -1;
            else
                return 0;
    }
    

    时间

    做了一些测量后,很明显‘不那么正统’的方法也比较慢。超过 20 次运行(每次进行 100,000,000 次迭代,每次迭代中比较不同的值对),我得到了平均时间和标准偏差(以秒为单位):

                Mean        Standard Deviation
    Value       0.732914    0.005000
    Pointer     0.655853    0.003895
    Null        0.353649    0.003448
    

    值和指针版本的差别很大(0.077s是标准差的很多倍),指针版本更快。所以使用传统的基于指针的比较器版本。 'Null' 时间使用一个比较器函数,该函数只返回 0 而不进行任何比较。

    代表输出行:

    Value:   0.730634 (less =  51517909, more =  48482090, equl =         1)
    Pointer: 0.684107 (less =  51517909, more =  48482090, equl =         1)
    Null:    0.351807 (less =         0, more =         0, equl = 100000000)
    

    测试代码

    两个比较器被重命名为compare_digests_val() 用于比较值,compare_digests_ptr() 用于比较指针。 Clock 类型和clk_* 函数是在我测试的平台上使用gettimeofday() 的高分辨率定时器包。显然,循环中的增量和统计数据的累积有相当大的开销,但这只是意味着比较器中的差异更显着。

    static int compare_digests_nul(const void *a, const void *b)
    {
        return 0;
    }
    
    static void time_comparisons(const char *tag, int (*compare)(const void *, const void *))
    {
        struct digest_line a = { 0, 0 };
        struct digest_line b = { 0, 0 };
        int less = 0;
        int more = 0;
        int equl = 0;
        Clock clk;
        char buffer[32];
        clk_init(&clk);
        clk_start(&clk);
        for (int i = 0; i < 100000000; i++)
        {
            int j = (*compare)(&a, &b);
            if (j < 0)
                less++;
            else if (j > 0)
                more++;
            else
                equl++;
            a.first  += 1234567890123ULL;
            a.second += 2345678901234ULL;
            b.first  += 7654321098765ULL;
            b.second += 8765432109876ULL;
        }
        clk_stop(&clk);
        printf("%-8s %s (less = %9d, more = %9d, equl = %9d)\n", tag,
               clk_elapsed_us(&clk, buffer, sizeof(buffer)),
               less, more, equl);
    }
    
    int main(void)
    {
        for (int i = 0; i < 20; i++)
        {
            time_comparisons("Value:",   compare_digests_val);
            time_comparisons("Pointer:", compare_digests_ptr);
            time_comparisons("Null:",    compare_digests_nul);
        }
        return 0;
    }
    

    【讨论】:

    • 为什么要创建结构的本地副本?使用const struct digest_line *aa = a; 然后aa-&gt;first 似乎更好。
    • 至少,它是一个完全有效的替代方案,当然我通常使用指针而不是局部变量。我不确定在使用合适的优化编译器时这两种表示法的性能是否存在可测量的差异。
    • 是的,我已经更正了比较函数中的有符号无符号问题。这看起来是正确的,非常感谢您的测试。
    猜你喜欢
    • 2011-05-30
    • 1970-01-01
    • 2012-02-22
    • 1970-01-01
    • 1970-01-01
    • 2015-05-02
    • 2012-05-01
    • 2013-03-05
    • 1970-01-01
    相关资源
    最近更新 更多