web-dev-qa-db-ja.com

ビットごとに0または1の確率で擬似ランダムビットを生成する高速な方法

通常、乱数ジェネレーターは、各位置で0または1を観測する確率が等しい(つまり50%)ビットストリームを返します。これを公平なPRNGと呼びましょう。

次のプロパティを持つ擬似ランダムビットの文字列を生成する必要があります:各位置で1を見る確率はpです(つまり、0を見る確率は1-pです)。パラメータpは0〜1の実数です。私の問題では、解像度が0.5%であることが起こります。つまり、0%、0.5%、1%、1.5%、...、99.5%、100%の値を取ることができます.

Pは確率であり、正確な端数ではないことに注意してください。 nビットのストリームで1に設定された実際のビット数は、二項分布B(n、p)に従う必要があります。

偏りのないPRNGを使用して各ビットの値(擬似コード)を生成できる単純な方法があります。

generate_biased_stream(n, p):
  result = []
  for i in 1 to n:
    if random_uniform(0, 1) < p:
      result.append(1)
    else:
      result.append(0)
  return result

このような実装は、各ビットごとに1回乱数ジェネレーター関数を呼び出すため、バイアスのないストリームを生成するものよりもはるかに低速です。偏りのないストリームジェネレーターは、Wordサイズごとに1回呼び出します(たとえば、1回の呼び出しで32または64のランダムビットを生成できます)。

ランダム性を少し犠牲にしても、より高速な実装が必要です。頭に浮かぶアイデアは、ルックアップテーブルを事前計算することです。pの200の可能な値のそれぞれについて、低速アルゴリズムを使用してC 8ビット値を計算し、テーブルに保存します。次に、高速アルゴリズムはこれらのいずれかをランダムに選択して、8つのスキュービットを生成します。

必要なメモリ量を確認するためのエンベロープ計算の裏側:Cは少なくとも256(可能な8ビット値の数)である必要があります。おそらくサンプリング効果を避けるためです。 1024としましょう。数はpに応じて変わるはずですが、単純にして平均を1024としましょう。pの値が200あるので、合計メモリ使用量は200 KBです。これは悪くなく、L2キャッシュ(256 KB)に収まる可能性があります。バイアスを導入するサンプリング効果があるかどうかを確認するために、まだ評価する必要があります。その場合、Cを増やす必要があります。

このソリューションの欠点は、多くの作業を行っても、一度に8ビットしか生成できないことです。一方、偏りのないPRNGは、わずかな算術命令で64ビットを一度に生成できます。

ルックアップテーブルの代わりにビット演算に基づいたより高速な方法があるかどうかを知りたいです。たとえば、乱数生成コードを直接変更して、各ビットにバイアスを導入します。これにより、公平なPRNGと同じパフォーマンスが得られます。


3月5日編集

ご提案ありがとうございました。多くの興味深いアイデアや提案をいただきました。ここにトップのものがあります:

  • Pの解像度が1/200ではなく1/256になるように、問題の要件を変更します。これにより、ビットをより効率的に使用でき、最適化の機会も増えます。この変更を行えると思います。
  • 算術コーディングを使用して、不偏ジェネレーターからのビットを効率的に消費します。上記の解像度の変更により、これははるかに簡単になります。
  • PRNGは非常に高速であるため、算術コーディングを使用すると、オーバーヘッドが発生するため、実際にコードが遅くなる可能性があると提案した人もいました。代わりに、最悪の場合のビット数を常に消費し、そのコードを最適化する必要があります。以下のベンチマークをご覧ください。
  • @riciはSIMDの使用を提案しました。これはいいアイデアで、常に固定ビット数を消費する場合にのみ機能します。

ベンチマーク(算術復号化なし)

注:多くの人が提案しているように、解像度を1/200から1/256に変更しました。

私は 複数の実装 ランダムなバイアスのない8ビットを受け取り、1つのバイアスビットを生成する単純なメソッドを記述しました。

  • SIMDなし
  • Agner Fogのvectorclassライブラリを使用するSIMDでは、 @ riciが示唆する
  • 組み込み関数を使用するSIMDを使用

2つの不偏擬似乱数ジェネレーターを使用します。

また、比較のために不偏PRNG=の速度も測定します。結果は以下のとおりです。


RNG: Ranvec1(Mersenne Twister for Graphics Processors + Multiply with Carry)

Method: Unbiased with 1/1 efficiency, SIMD=vectorclass (incorrect, baseline)
Gbps/s: 16.081 16.125 16.093 [Gb/s]
Number of ones: 536,875,204 536,875,204 536,875,204
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency
Gbps/s: 0.778 0.783 0.812 [Gb/s]
Number of ones: 104,867,269 104,867,269 104,867,269
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=vectorclass
Gbps/s: 2.176 2.184 2.145 [Gb/s]
Number of ones: 104,859,067 104,859,067 104,859,067
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=intrinsics
Gbps/s: 2.129 2.151 2.183 [Gb/s]
Number of ones: 104,859,067 104,859,067 104,859,067
Theoretical   : 104,857,600

SIMDは、スカラー法と比較してパフォーマンスを3倍向上させます。予想されるように、バイアスのないジェネレータよりも8倍遅いです。

最速のバイアス発生器は2.1 Gb/sを達成します。


RNG: xorshift128plus

Method: Unbiased with 1/1 efficiency (incorrect, baseline)
Gbps/s: 18.300 21.486 21.483 [Gb/s]
Number of ones: 536,867,655 536,867,655 536,867,655
Theoretical   : 104,857,600

Method: Unbiased with 1/1 efficiency, SIMD=vectorclass (incorrect, baseline)
Gbps/s: 22.660 22.661 24.662 [Gb/s]
Number of ones: 536,867,655 536,867,655 536,867,655
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency
Gbps/s: 1.065 1.102 1.078 [Gb/s]
Number of ones: 104,868,930 104,868,930 104,868,930
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=vectorclass
Gbps/s: 4.972 4.971 4.970 [Gb/s]
Number of ones: 104,869,407 104,869,407 104,869,407
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=intrinsics
Gbps/s: 4.955 4.971 4.971 [Gb/s]
Number of ones: 104,869,407 104,869,407 104,869,407
Theoretical   : 104,857,600

Xorshiftの場合、SIMDはスカラー法と比較してパフォーマンスを5倍向上させます。バイアスのないジェネレーターよりも4倍遅いです。これはxorshiftのスカラー実装であることに注意してください。

最速のバイアス発生器は4.9 Gb/sを達成します。


RNG: xorshift128plus_avx2

Method: Unbiased with 1/1 efficiency (incorrect, baseline)
Gbps/s: 18.754 21.494 21.878 [Gb/s]
Number of ones: 536,867,655 536,867,655 536,867,655
Theoretical   : 104,857,600

Method: Unbiased with 1/1 efficiency, SIMD=vectorclass (incorrect, baseline)
Gbps/s: 54.126 54.071 54.145 [Gb/s]
Number of ones: 536,874,540 536,880,718 536,891,316
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency
Gbps/s: 1.093 1.103 1.063 [Gb/s]
Number of ones: 104,868,930 104,868,930 104,868,930
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=vectorclass
Gbps/s: 19.567 19.578 19.555 [Gb/s]
Number of ones: 104,836,115 104,846,215 104,835,129
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=intrinsics
Gbps/s: 19.551 19.589 19.557 [Gb/s]
Number of ones: 104,831,396 104,837,429 104,851,100
Theoretical   : 104,857,600

この実装では、AVX2を使用して、4つのバイアスのないxorshiftジェネレーターを並行して実行します。

最速のバイアス発生器は19.5 Gb/sを達成します。

算術復号化のベンチマーク

簡単なテストでは、算術復号化コードがPRNGではなくボトルネックであることを示しています。したがって、私は最も高価なPRNGのみをベンチマークしています。


RNG: Ranvec1(Mersenne Twister for Graphics Processors + Multiply with Carry)

Method: Arithmetic decoding (floating point)
Gbps/s: 0.068 0.068 0.069 [Gb/s]
Number of ones: 10,235,580 10,235,580 10,235,580
Theoretical   : 10,240,000

Method: Arithmetic decoding (fixed point)
Gbps/s: 0.263 0.263 0.263 [Gb/s]
Number of ones: 10,239,367 10,239,367 10,239,367
Theoretical   : 10,240,000

Method: Unbiased with 1/1 efficiency (incorrect, baseline)
Gbps/s: 12.687 12.686 12.684 [Gb/s]
Number of ones: 536,875,204 536,875,204 536,875,204
Theoretical   : 104,857,600

Method: Unbiased with 1/1 efficiency, SIMD=vectorclass (incorrect, baseline)
Gbps/s: 14.536 14.536 14.536 [Gb/s]
Number of ones: 536,875,204 536,875,204 536,875,204
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency
Gbps/s: 0.754 0.754 0.754 [Gb/s]
Number of ones: 104,867,269 104,867,269 104,867,269
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=vectorclass
Gbps/s: 2.094 2.095 2.094 [Gb/s]
Number of ones: 104,859,067 104,859,067 104,859,067
Theoretical   : 104,857,600

Method: Biased with 1/8 efficiency, SIMD=intrinsics
Gbps/s: 2.094 2.094 2.095 [Gb/s]
Number of ones: 104,859,067 104,859,067 104,859,067
Theoretical   : 104,857,600

単純な固定小数点法は0.25 Gb/sを達成しますが、単純なスカラー法は3倍高速で、単純なSIMD法は8倍高速です。算術復号化方法をさらに最適化および/または並列化する方法があるかもしれませんが、その複雑さのため、ここでやめて単純なSIMD実装を選択することにしました。

助けてくれてありがとう。

57
o9000

256の可能な値に基づいてpを近似する準備ができていて、個々のビットが互いに独立している均一な値を生成できるPRNGがある場合、ベクトル化された比較を使用して、単一の乱数から複数のバイアスビットを生成できます。

これは、(1)乱数の品質に不安がある場合、および(2)同じバイアスで多数のビットが必要な場合にのみ行う価値があります。 2番目の要件は、次のように、提案されたソリューションを批判する元の質問によって暗示されているようです。「このソリューションの欠点は、多くの作業で、一度に8ビットしか生成できないことです。 PRNGは、ほんの数個の算術命令で64を一度に生成できます。 "ここで、意味はuseful1回の呼び出しでバイアスビットの大きなブロックを生成します。

乱数の品質は難しい課題です。測定することは不可能ではないにしても困難であるため、さまざまな人々が「ランダム性」のさまざまな側面を強調および/または過小評価するさまざまなメトリックを提案します。一般的に、乱数生成の速度と「品質」の低下をトレードオフすることが可能です。これを行う価値があるかどうかは、正確なアプリケーションによって異なります。

乱数品質の最も簡単な可能なテストには、個々の値の分布とジェネレーターのサイクル長が含まれます。 CライブラリRandおよびPosix random関数の標準実装は、通常、分布テストに合格しますが、サイクルの長さは長時間実行されるアプリケーションには適切ではありません。

ただし、これらのジェネレーターは通常非常に高速です。randomのglibc実装には数サイクルしか必要ありませんが、従来の線形合同ジェネレーター(LCG)には乗算と加算が必要です。 (または、glibc実装の場合、上記のうち3つが31ビットを生成します。)それが品質要件に十分であれば、特にバイアス確率が頻繁に変化する場合、最適化を試みるポイントはほとんどありません。

サイクルの長さは、予想されるサンプルの数よりもはるかに長くする必要があることに注意してください。理想的には、その数の2乗よりも大きくなければならないため、サイクル長が2の線形合同ジェネレーター(LCG)31 ギガバイトのランダムデータを生成する場合は適切ではありません。サイクル長が約2であると主張されているGnu三項非線形加法フィードバックジェネレーターでも35、何百万ものサンプルを必要とするアプリケーションには使用しないでください。

テストするのがはるかに難しい別の品質問題は、連続したサンプルの独立性に関連しています。繰り返しが開始されると、生成された乱数は履歴値と正確に相関するため、このメトリックでは短いサイクル長は完全に失敗します。 Gnu三項アルゴリズムは、そのサイクルは長くなりますが、iという事実の結果として、明確な相関関係があります。番目 生成された乱数、ri、常に2つの値のいずれかri-3+ri− 31 またはri-3+ri− 31+1。これは、特にベルヌーイの実験で、 驚くべき または少なくとも 不可解 の結果になります。

Agner Fogの便利な ベクタークラスライブラリ を使用した実装を次に示します。これは、SSE組み込み関数の多くの迷惑な詳細を抽象化し、高速に役立つ ベクトル化された乱数ジェネレーターspecial.Zipvectorclass.Zip archive)。これにより、256ビットPRNGへの8回の呼び出しから256ビットを生成できます。フォグ博士がメルセンヌのツイスターでさえ品質の問題を発見した理由とその提案された解決策を読むことができます。本当にコメントする資格はありませんが、少なくとも、私が試したベルヌーイの実験で期待される結果が得られるようです。

#include "vectorclass/vectorclass.h"
#include "vectorclass/ranvec1.h"

class BiasedBits {
  public:
    // Default constructor, seeded with fixed values
    BiasedBits() : BiasedBits(1)  {}
    // Seed with a single seed; other possibilities exist.
    BiasedBits(int seed) : rng(3) { rng.init(seed); }

    // Generate 256 random bits, each with probability `p/256` of being 1.
    Vec8ui random256(unsigned p) {
      if (p >= 256) return Vec8ui{ 0xFFFFFFFF };
      Vec32c output{ 0 };
      Vec32c threshold{ 127 - p };
      for (int i = 0; i < 8; ++i) {
        output += output;
        output -= Vec32c(Vec32c(rng.uniform256()) > threshold);
      }
      return Vec8ui(output);
    }

  private:
    Ranvec1 rng;
};

私のテストでは、260ミリ秒で268435456ビット(ナノ秒あたり1ビット)を生成してカウントしました。テストマシンはi5なので、AVX2はありません。 YMMV。

実際の使用例では、pに201個の可能な値が設定されているため、8ビットのしきい値の計算は非常に不正確になります。その不正確さが望ましくない場合は、2倍の数の乱数を生成することを犠牲にして、16ビットのしきい値を使用するように上記を適合させることができます。

または、10ビットのしきい値に基づいてベクトル化を手作業で行うこともできます。これにより、10ビットごとにボローをチェックすることにより、ベクトル化されたしきい値比較を行う標準のビット操作ハックを使用して、0.5%の増分に非常に良い近似が得られます値のベクトルと繰り返ししきい値の減算。たとえば、std::mt19937_64、各64ビットの乱数ごとに平均6ビットが得られます。

25
rici

できることの1つは、基礎となる不偏ジェネレーターから複数回サンプリングし、いくつかの32ビットまたは64ビットのワードを取得してから、ビットごとのブール演算を実行することです。例として、4ワードの場合b1,b2,b3,b4、次の分布を取得できます。

式| p(ビットは1)
 ----------------------- + ------------- 
 b1&b2&b3&b4 | 6.25%
 b1&b2&b3 | 12.50%
 b1&b2&(b3 | b4)| 18.75%
 b1&b2 | 25.00%
 b1 | (b2&(b3 | b4))| 31.25%
 b1&(b2 | b3)| 37.50%
 b1&(b2 | b3 | b4))| 43.75%
 b1 | 50.00%

同様の構造は、より細かい解像度でも作成できます。それは少し面倒になり、さらに多くのジェネレーター呼び出しが必要ですが、少なくともビットごとに1つではありません。これはa3fの答えに似ていますが、おそらく実装が簡単で、0xFニブル。

希望する0.5%の解像度では、バイアスのかかった1つの単語に対して8つのバイアスのない単語が必要であり、(0.5 ^ 8)= 0.390625%の解像度が得られることに注意してください。

28
mindriot

情報理論的な観点から見ると、ビットのバイアスストリーム(p != 0.5を使用)には、バイアスのないストリームよりもless情報があるため、理論的にはバイアスされた出力ストリームの単一ビットを生成するには、1ビットの不偏入力よりも(平均で)lessを取る必要があります。たとえば、p = 0.1を持つベルヌーイ確率変数の エントロピー-0.1 * log2(0.1) - 0.9 * log2(0.9)ビットで、これは0.469ビット前後です。 p = 0.1の場合、バイアスのない入力ビットごとに2ビットを少し超える出力ストリームを生成できるはずです。

以下に、バイアスビットを生成する2つの方法を示します。両方とも、可能な限り少ない入力不偏ビットを必要とするという意味で、最適に近い効率を達成します。

方法1:算術(デ)コーディング

実用的な方法は、 alexisからの回答 で既に説明したように、 arithmetic(de)coding を使用して不偏入力ストリームをデコードすることです。この単純なケースでは、何かをコーディングするのは難しくありません。これを行う最適化されていない疑似コード(咳、Python)は次のとおりです。

import random

def random_bits():
    """
    Infinite generator generating a stream of random bits,
    with 0 and 1 having equal probability.
    """
    global bit_count  # keep track of how many bits were produced
    while True:
        bit_count += 1
        yield random.choice([0, 1])

def bernoulli(p):
    """
    Infinite generator generating 1-bits with probability p
    and 0-bits with probability 1 - p.
    """
    bits = random_bits()

    low, high = 0.0, 1.0
    while True:
        if high <= p:
            # Generate 1, rescale to map [0, p) to [0, 1)
            yield 1
            low, high = low / p, high / p
        Elif low >= p:
            # Generate 0, rescale to map [p, 1) to [0, 1)
            yield 0
            low, high = (low - p) / (1 - p), (high - p) / (1 - p)
        else:
            # Use the next random bit to halve the current interval.
            mid = 0.5 * (low + high)
            if next(bits):
                low = mid
            else:
                high = mid

以下に使用例を示します。

import itertools
bit_count = 0

# Generate a million deviates.
results = list(itertools.islice(bernoulli(0.1), 10**6))

print("First 50:", ''.join(map(str, results[:50])))
print("Biased bits generated:", len(results))
print("Unbiased bits used:", bit_count)
print("mean:", sum(results) / len(results))

上記は、次のサンプル出力を示します。

First 50: 00000000000001000000000110010000001000000100010000
Biased bits generated: 1000000
Unbiased bits used: 469036
mean: 0.100012

約束どおり、ソースバイアスなしストリームから50万未満を使用して、100万ビットの出力バイアスストリームを生成しました。

最適化の目的で、これをC/C++に変換する場合、浮動小数点ではなく整数ベースの固定小数点演算を使用してこれをコーディングするのが理にかなっています。

方法2:整数ベースのアルゴリズム

算術復号化方法を変換して整数を直接使用するのではなく、こちらの方が簡単です。もはや算術復号化ではありませんが、完全に無関係ではなく、上記の浮動小数点バージョンとほぼ同じ出力バイアスビット/入力バイアスビット比を達成します。すべての数量が符号なし32ビット整数に収まるように編成されているため、C/C++に簡単に変換できるはずです。コードはp1/200の正確な倍数である場合に特化されていますが、このアプローチは合理的に小さな分母を持つ有理数として表現できるpに対して機能します。

def bernoulli_int(p):
    """
    Infinite generator generating 1-bits with probability p
    and 0-bits with probability 1 - p.

    p should be an integer multiple of 1/200.
    """
    bits = random_bits()
    # Assuming that p has a resolution of 0.05, find p / 0.05.
    p_int = int(round(200*p))

    value, high = 0, 1
    while True:
        if high < 2**31:
            high = 2 * high
            value = 2 * value + next(bits)
        else:
            # Throw out everything beyond the last multiple of 200, to
            # avoid introducing a bias.
            discard = high - high % 200
            split = high // 200 * p_int
            if value >= discard:  # rarer than 1 time in 10 million
                value -= discard
                high -= discard
            Elif value >= split:
                yield 0
                value -= split
                high = discard - split
            else:
                yield 1
                high = split

重要な点は、whileループの先頭に到達するたびに、value[0, high)のすべての整数に均一に分散され、以前に出力されたすべてのビットに依存しないことです。完全な正確さよりも速度を重視する場合は、discardvalue >= discardブランチを取り除くことができます。これは、01を正確に正しい確率で出力するためのものです。その複雑さを省くと、代わりに正しい確率almostが得られます。また、pの解像度を1/256ではなく1/200に等しくすると、時間のかかる可能性のある除算とモジュロ演算をビット演算に置き換えることができます。

以前と同じテストコードで、bernoulliの代わりにbernoulli_intを使用すると、p=0.1に対して次の結果が得られます。

First 50: 00000010000000000100000000000000000000000110000100
Biased bits generated: 1000000
Unbiased bits used: 467997
mean: 0.099675
17
Mark Dickinson

理論的に最適な動作が得られます。つまり、乱数ジェネレーターを真に最小限の使用にし、model 算術コーディング を使用してこれにアプローチする場合、正確に任意の確率p

算術コーディングは、メッセージを数値範囲のサブインターバルとして表すデータ圧縮の形式です。理論的に最適なエンコーディングを提供し、各入力シンボルに小数のビットを使用できます。

考えは次のとおりです。確率1と確率pである一連のランダムビットがあると想像してください。便宜上、ビットがゼロになる確率にqを代わりに使用します。 (q = 1-p)。算術コーディングは、数値範囲の各ビット部分に割り当てます。最初のビットについて、入力が0の場合は間隔[0、q)を割り当て、入力が1の場合は間隔[q、1)を割り当てます。後続のビットは、現在の範囲の比例部分間隔を割り当てます。たとえば、q = 1/3入力1 0 0はこのようにエンコード:

Initially       [0, 1),             range = 1
After 1         [0.333, 1),         range = 0.6666        
After 0         [0.333, 0.5555),    range = 0.2222   
After 0         [0.333, 0.407407),  range = 0.074074

最初の数字1、は、範囲の上位3分の2(1-q)を選択します。 2番目の数字は、thatの下3分の1を選択します。最初と2番目のステップの後、間隔は中間点にまたがります。しかし、3番目のステップの後は完全に中間点より下にあるため、最初の圧縮数字を出力できます:0。プロセスは続行され、特別なEOF記号がターミネーターとして追加されます。

これはあなたの問題と何の関係がありますか?圧縮された出力には、確率が等しいランダムなゼロと1が含まれます。したがって、確率pでビットを取得するには、RNGの出力が上記の算術コーディングの結果であると仮定し、を適用しますつまり、ビットを読み取り、ライン間隔をますます細かく分割します。たとえば、01 RNGから、範囲[0.25、0.5)になります。十分な出力が「デコード」されるまでビットを読み取り続けます。解凍を模倣しているため、入れたよりも多くのランダムなビットが出力されます。算術コーディングは理論的に最適であるため、可能な方法はありませんランダム性を犠牲にすることなく、RNG出力をより偏ったビットに変換します。真の最大値を取得しています。

キャッチは、数行のコードでこれを行うことができないことであり、私があなたを指すことができるライブラリを知りません(あなたが使用できるものがなければなりませんが)。それでも、それは非常に簡単です。 上記の記事 は、Cの汎用エンコーダーおよびデコーダーのコードを提供します。これは非常に簡単で、任意の確率で複数の入力シンボルをサポートします。あなたの場合、確率モデルは自明であるため、はるかに簡単な実装が可能です( Mark Dickinson's answer が示すように)。拡張使用の場合、各ビットに対して多くの浮動小数点計算を行わない堅牢な実装を作成するには、もう少し作業が必要になります。

Wikipedia には、基数の変更と見なされる算術エンコーディングの興味深い議論もあります。これは、タスクを表示する別の方法です。

9
alexis

1が現れる確率は6,25%(1/16)であるとしましょう。 4ビット番号には16の可能なビットパターンがあります:0000,0001, ..., 1110,1111

ここで、以前と同じように乱数を生成し、すべての1111ニブル境界で1そして他のすべてを0

他の確率に応じて適宜調整してください。

9
a3f

ええと、擬似乱数ジェネレーターは一般に非常に高速です。これがどの言語なのかわからない(おそらくPython)が、「result.append」(ほぼ確実にメモリ割り当てを含む)は、「random_uniform」(ちょっとした計算を行う)よりも遅い可能性が高い。

このコードのパフォーマンスを最適化する場合:

  1. それが問題であることを確認してください。最適化には少し手間がかかり、コードの保守が難しくなります。必要でない限り、それらをしないでください。
  2. それをプロファイルします。いくつかのテストを実行して、コードのどの部分が実際に最も遅いかを判断します。これらは、高速化するために必要な部品です。
  3. 変更を行い、実際に高速であることを確認します。コンパイラはかなり賢いです。多くの場合、明確なコードは、より複雑なものがより高速に見えるかもしれないより良いコードにコンパイルされます。

コンパイルされた言語(コンパイルされたJITを含む)で作業している場合、制御のすべての転送(if、while、関数呼び出しなど)でパフォーマンスが低下します。できることを排除します。メモリの割り当ても(通常)非常に高価です。

インタプリタ言語で作業している場合、すべての賭けは無効です。最も単純なコードが最も可能性が高いです。インタプリタのオーバーヘッドは、あなたがしていることは何でもd化しますので、できる限りその作業を減らしてください。

あなたのパフォーマンスの問題はどこにあるかだけ推測できます:

  1. メモリ割り当て。配列をフルサイズで事前に割り当て、後でエントリを入力します。これにより、エントリの追加中にメモリを再割り当てする必要がなくなります。
  2. 枝。結果などをキャストすることで、「if」を回避できる場合があります。これはコンパイラに大きく依存します。アセンブリ(またはプロファイル)をチェックして、目的どおりに機能することを確認します。
  3. 数値タイプ。乱数ジェネレーターがネイティブで使用するタイプを見つけ、そのタイプで算術を行います。たとえば、ジェネレータが32ビットの符号なし整数を自然に返す場合、最初にその範囲に「p」をスケーリングし、それを比較に使用します。

ところで、可能な限り最小のランダム性を使用したい場合は、「算術コーディング」を使用してランダムストリームをデコードします。速くはありません。

8
Dalias

正確な結果を得る1つの方法は、最初にkビットブロックに対して2項分布に続く1ビットの数をランダムに生成し、次にいずれかの方法を使用して正確にそのビット数でkビットのワードを生成することです ここ 。たとえば、mic006による方法では約log k kビットの乱数のみが必要で、私の場合は1つだけ必要です。

7
Falk Hüffner

ランダムビットのジェネレーターにアクセスできると仮定すると、ビットごとにpと比較する値を生成し、生成された値が以下または以下であることを証明できるとすぐに中止できます。 -equal-to p

次の手順に従って、特定の確率pでストリームに1つのアイテムを作成します。

  1. 0.で始まるバイナリ
  2. ランダムビットを追加します。 1が描画されていると仮定すると、0.1が得られます
  3. 結果が(バイナリ表記で)pよりも確実に小さい場合は、1を出力します
  4. 結果がp以上である可能性が高い場合、0を出力します
  5. それ以外の場合(どちらも除外できない場合)、手順2に進みます。

バイナリ表記のp0.1001101...であると仮定しましょう。このプロセスが0.00.10000.10010、...のいずれかを生成する場合、値はp以上になることはできません。 0.110.1010.100111、...のいずれかが生成された場合、値はpより小さくできません。

私には、この方法は予想して約2つのランダムビットを使用しているように見えます。算術コーディング(Mark Dickinsonの回答に示されているように)は、固定pの-​​最大バイアスビットごとに1ランダムビット(平均)を消費します。 pを変更するコストは不明です。

6
krlmlr

Pが0に近い場合、n番目のビットが1である最初のビットである確率を計算できます。次に、0から1の間の乱数を計算し、それに応じてnを選択します。たとえば、p = 0.005(0.5%)で、乱数が0.638128の場合、n = 321を計算できます(ここでは推測しています)。したがって、321 0ビットと1ビットセットで埋めます。

Pが1に近い場合、pの代わりに1-pを使用し、1ビットと1つの0ビットを設定します。

Pが1または0に近くない場合、8ビットの256シーケンスすべてのテーブルを作成し、累積確率を計算してから乱数を取得し、累積確率の配列でバイナリ検索を実行し、8ビットを設定できます。

6
gnasher729

何をする

この実装は、「/ dev/urandom」特殊文字ファイルのインターフェースを介してランダムデバイスカーネルモジュールをsingle呼び出しして、すべてを表すのに必要な数のランダムデータを取得します。指定された解像度の値。可能な最大解像度は1/256 ^ 2であるため、0.005は次のように表すことができます。

328/256 ^ 2、

すなわち:

解像度:256 * 256

x:328

エラー0.000004883で。

どうやって

実装は、ビット数bits_per_byteを計算します。これは、特定の解像度を処理するために必要な均一に分散されたビットの数です。つまり、すべての@resolution値を表します。その後、ランダム化デバイス(URANDOM_DEVICEが定義されている場合は "/ dev/urandom"を1回呼び出します。十分なエントロピー(ビット単位))必要な数の均一に分散されたバイトを取得し、ランダムバイトの配列rnd_bytesを埋めます。最後に、rnd_bytes配列の各bytes_per_byteバイトから各ベルヌーイサンプルごとに必要なビット数を読み取り、これらのビットの整数値をx/resolutionで指定された単一ベルヌーイ結果の成功確率と比較します。値がヒットした場合、つまり、[0、x/resolution)セグメントとして任意に選択したx/resolution長さのセグメントに該当する場合、成功を記録し、結果の配列に1を挿入します。


ランダムデバイスから読み取る:

/* if defined use /dev/urandom (will not block),
 * if not defined use /dev/random (may block)*/
#define URANDOM_DEVICE 1

/*
 * @brief   Read @outlen bytes from random device
 *          to array @out.
 */
int
get_random_samples(char *out, size_t outlen)
{
    ssize_t res;
#ifdef URANDOM_DEVICE
    int fd = open("/dev/urandom", O_RDONLY);
    if (fd == -1) return -1;
    res = read(fd, out, outlen);
    if (res < 0) {
        close(fd);
        return -2;
    }
#else
    size_t read_n;
    int fd = open("/dev/random", O_RDONLY);
    if (fd == -1) return -1;
    read_n = 0;
    while (read_n < outlen) {
       res = read(fd, out + read_n, outlen - read_n);
       if (res < 0) {
           close(fd);
           return -3;
       }
       read_n += res;
    }
#endif /* URANDOM_DEVICE */
    close(fd);
    return 0;
}

ベルヌーイサンプルのベクトルを入力します。

/*
 * @brief   Draw vector of Bernoulli samples.
 * @details @x and @resolution determines probability
 *          of success in Bernoulli distribution
 *          and accuracy of results: p = x/resolution.
 * @param   resolution: number of segments per sample of output array 
 *          as power of 2: max resolution supported is 2^24=16777216
 * @param   x: determines used probability, x = [0, resolution - 1]
 * @param   n: number of samples in result vector
 */
int
get_bernoulli_samples(char *out, uint32_t n, uint32_t resolution, uint32_t x)
{
    int res;
    size_t i, j;
    uint32_t bytes_per_byte, Word;
    unsigned char *rnd_bytes;
    uint32_t uniform_byte;
    uint8_t bits_per_byte;

    if (out == NULL || n == 0 || resolution == 0 || x > (resolution - 1))
        return -1;

    bits_per_byte = log_int(resolution);
    bytes_per_byte = bits_per_byte / BITS_PER_BYTE + 
                        (bits_per_byte % BITS_PER_BYTE ? 1 : 0);
    rnd_bytes = malloc(n * bytes_per_byte);
    if (rnd_bytes == NULL)
        return -2;
    res = get_random_samples(rnd_bytes, n * bytes_per_byte);
    if (res < 0)
    {
        free(rnd_bytes);
        return -3;
    }

    i = 0;
    while (i < n)
    {
        /* get Bernoulli sample */
        /* read byte */
        j = 0;
        Word = 0;
        while (j < bytes_per_byte)
        {
            Word |= (rnd_bytes[i * bytes_per_byte + j] << (BITS_PER_BYTE * j));
            ++j;
        }
        uniform_byte = Word & ((1u << bits_per_byte) - 1);
        /* decision */
        if (uniform_byte < x)
            out[i] = 1;
        else
            out[i] = 0;
        ++i;
    }

    free(rnd_bytes);    
    return 0;
}

使用法:

int
main(void)
{
    int res;
    char c[256];

    res = get_bernoulli_samples(c, sizeof(c), 256*256, 328); /* 328/(256^2) = 0.0050 */
    if (res < 0) return -1;

    return 0;
}

完全なコード結果

5
4pie0