【问题标题】:weighted RNG speed problem in C++C ++中的加权RNG速度问题
【发布时间】:2010-05-10 09:12:12
【问题描述】:

编辑:澄清一下,问题出在 second 算法上。

我有一些 C++ 代码可以从 52 张卡片组中采样卡片,效果很好:

void sample_allcards(int table[5], int holes[], int players) {
    int temp[5 + 2 * players];
    bool try_again;
    int c, n, i;

    for (i = 0; i < 5 + 2 * players; i++) {
        try_again = true;
        while (try_again == true) {
            try_again = false;
            c = fast_rand52();
            // reject collisions
            for (n = 0; n < i + 1; n++) {
                try_again = (temp[n] == c) || try_again;
            }
            temp[i] = c;
        }
    }
    copy_cards(table, temp, 5);
    copy_cards(holes, temp + 5, 2 * players);
}

我正在实现代码以根据已知分布(存储为二维表)对底牌进行采样。我的代码如下:

void sample_allcards_weighted(double weights[][HOLE_CARDS], int table[5], int holes[], int players) {
    // weights are distribution over hole cards
    int temp[5 + 2 * players];
    int n, i;

    // table cards
    for (i = 0; i < 5; i++) {
        bool try_again = true;
        while (try_again == true) {
            try_again = false;
            int c = fast_rand52();
            // reject collisions
            for (n = 0; n < i + 1; n++) {
                try_again = (temp[n] == c) || try_again;
            }
            temp[i] = c;
        }
    }

    for (int player = 0; player < players; player++) {
        // hole cards according to distribution
        i = 5 + 2 * player;
        bool try_again = true;
        while (try_again == true) {
            try_again = false;
            // weighted-sample c1 and c2 at once
            // h is a number < 1325
            int h = weighted_randi(&weights[player][0], HOLE_CARDS);
            // i2h uses h and sets temp[i] to the 2 cards implied by h
            i2h(&temp[i], h);
            // reject collisions
            for (n = 0; n < i; n++) {
                try_again = (temp[n] == temp[i]) || (temp[n] == temp[i+1]) || try_again;
            }
        }
    }

    copy_cards(table, temp, 5);
    copy_cards(holes, temp + 5, 2 * players);
}

我的问题?加权采样算法慢了 10 倍。速度对我的应用程序非常重要。

有没有办法将我的算法速度提高到更合理的程度?我在实施过程中做错了吗?

谢谢。

编辑:有人问我这个功能,我应该发布它,因为它是关键

inline int weighted_randi(double *w, int num_choices) {
double r = fast_randd();
double threshold = 0;
int n;

for (n = 0; n < num_choices; n++) {
    threshold += *w;
    if (r <= threshold) return n;
    w++;
}
// shouldn't get this far
cerr << n << "\t" << threshold << "\t" << r << endl;
assert(n < num_choices);
return -1;

}

...而 i2h() 基本上只是一个数组查找。

【问题讨论】:

    标签: c++ random performance weighting


    【解决方案1】:

    您的拒绝冲突正在将 O(n) 算法转变为(我认为)O(n^2) 操作。

    有两种方法可以从一副牌中选择牌:洗牌和出牌,或者选择套牌直到套牌中的元素是唯一的;你正在做后者,这需要大量的回溯。

    我没有看代码的详细信息,只是快速扫描。

    【讨论】:

    • 确实,std::random_shuffle 应该既简单又无偏见(就底层随机数生成器而言 - 默认情况下是 rand() % x? - 是无偏见的)。
    • 谢谢;这是对第一个算法的一个很好的观察。我很可能会吸收您的见解。然而,它完全避免了这个问题;如何权衡卡片的选择?
    • 澄清一下,我使用的是 Mersenne twister RNG,而不是 rand(),但我当然可以实现自己的 shuffle。
    • 使用 random_shuffle 使均匀采样的代码(第一个例程)提高了约 30%。
    【解决方案2】:

    您可以通过用位掩码替换检查卡片是否被占用的所有循环来获得一些速度,例如,对于 52 张卡片的池,我们可以防止这样的冲突:

    DWORD dwMask[2] = {0}; //64 bits
    //...
    int nCard;
    while(true)
    {
        nCard = rand_52();
        if(!(dwMask[nCard >> 5] & 1 << (nCard & 31)))
        {
            dwMask[nCard >> 5] |= 1 << (nCard & 31);
            break;
        }
    }
    //...
    

    【讨论】:

      【解决方案3】:

      我的猜测是重试循环中的 memcpy(1326*sizeof(double)) 。好像没变,是不是应该每次都复制一下?

      【讨论】:

      • 好地方,谢谢,不知道我是怎么错过的。我只是尝试将其移出循环,但没有太大帮助。
      • 您是否将其移出两个嵌套循环(for 和 while),还是不可能?
      • 我通过简单地执行 int h = weighted_randi(&weights[player][0], HOLE_CARDS); 消除了 memcpy;
      • 太糟糕了,这并没有什么不同(但它确实简化了代码)。我最好的选择是也看看 weighted_randi() 和 i2h()。当没有任何明显的变化时,测量(分析或按照 Mike 的建议)
      【解决方案4】:

      与其告诉你问题是什么,让我建议你如何找到它。要么 1) 在 IDE 中单步执行它,要么 2) randomly halt it 看看它在做什么。

      也就是说,如果您拒绝大多数样本,那么通过拒绝抽样可能会花费不合理的时间。

      【讨论】:

        【解决方案5】:

        一旦将 try_again 设置为 true,您的内部“try_again”for 循环应该立即停止 - 在您知道需要重试之后再做更多的工作是没有意义的。

        for (n = 0; n < i && !try_again; n++) {
            try_again = (temp[n] == temp[i]) || (temp[n] == temp[i+1]);
        }
        

        【讨论】:

          【解决方案6】:

          回答关于从加权集合中挑选的第二个问题也有一个算法替换,它应该不那么复杂。这是基于预先计算的不需要重新计算的原则。

          在普通选择中,您有整数个 bin,这使得选择 bin 成为 O(1) 操作。您的 weighted_randi 函数具有实际长度的 bin,因此您当前版本中的选择在 O(n) 时间内运行。由于您没有说(但确实暗示)权重向量 w 是恒定的,所以我假设它是恒定的。

          您对箱子的宽度不感兴趣,本身,您对每次调用 weighted_randi 时使用变量 @ 重新计算的边缘位置感兴趣987654324@。如果w 的恒定性为真,则预先计算一个边列表(即所有*wthreshold 的值)是您的O(n) 步骤,它只需要做一次。如果将结果放入(自然)有序列表中,对所有未来调用进行二分搜索会产生 O(log n) 时间复杂度,所需空间仅增加 sizeof w / sizeof w[0]

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-09-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-07-23
            • 1970-01-01
            • 1970-01-01
            • 2011-11-15
            相关资源
            最近更新 更多