Are there any issues with making random_generator static here?

355 Views Asked by At

I need to generate large number of UUIDs. If I don't make rg static, it would take lot of time in default constructing it every time. Is there anything wrong if I make it static, would it hurt uniqueness of uuids in anyway?

Is there any better way to do it?

using namespace boost::uuids;

uuid generateUUID() {
    static random_generator rg; // here
    return rg();
}

void someFunction() {
    for (int i = 0; i < 1000000; ++i) {
        uuid id = generateUUID();
        // use id
    }
}
2

There are 2 best solutions below

0
On

No, there is no problem with that.

0
On

I need to generate large number of UUIDs. If I don't make rg static, it would take lot of time in default constructing it every time. Is there anything wrong if I make it static, would it hurt uniqueness of uuids in anyway?

No, that's fine...

Is there any better way to do it?

... maybe, depending on the number of iterations you're going to need.

You see the (minor) trouble with static objects at function scope is in the wording of the standard, which dictates that:

a) The object is constructed the first time code flows over the statement and

b) That this construction must be thread-safe.

In practice this means that the code in generateUUID() must test whether it has already constructed the object. This involves a memory fetch and a conditional branch.

Here is the assembler code output from your function when compiled on apple clang 7.0 with -O3:

__Z12generateUUIDv:                     ## @_Z12generateUUIDv
Lfunc_begin0:
    .cfi_startproc
    .cfi_personality 155, ___gxx_personality_v0
    .cfi_lsda 16, Lexception0
## BB#0:
    pushq   %rbp
Ltmp3:
    .cfi_def_cfa_offset 16
Ltmp4:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp5:
    .cfi_def_cfa_register %rbp
    pushq   %rbx
    subq    $24, %rsp
Ltmp6:
    .cfi_offset %rbx, -24
    movb    __ZGVZ12generateUUIDvE2rg(%rip), %al  ## MEMORY FETCH
    testb   %al, %al                              ## TEST
    jne LBB0_4                                    ## CONDITIONAL BRANCH
## BB#1:
    leaq    __ZGVZ12generateUUIDvE2rg(%rip), %rdi ## another check in case
    callq   ___cxa_guard_acquire                  ## 2+ threads are racing
    testl   %eax, %eax
    je  LBB0_4
...                                               ## object constructed here

This is necessary because anyone could call the function. The optimiser can't know whether the construction has happened before or not. So we're stuck with 3 extra instructions we don't really want because we chose the luxury of having our object constructed when needed.

And this means that if you're in a tight loop you may want to avoid all these redundant tests.

The question is, what is the cost of creating a generator vs the cost of a 3 million instruction executions and a (hopefully) correctly predicted branch?

At some large number of iterations, the following code actually becomes more efficient (if that really matters to you):

void someFunction() {
    // one generator used for the duration of the function
    random_generator rg;
    for (int i = 0; i < 1000000; ++i) {
        uuid id = rg();
        // use id
    }
}

with the most efficient use of a static generator being this:

using namespace boost::uuids;

static random_generator rg; // constructed at some point before main() is called
                            // we no longer require flag tests

void someFunction() {
    for (int i = 0; i < 1000000; ++i) {
        uuid id = rg();
        // use id
    }
}

However this is slightly less convenient since we must now take care that nothing uses rg until main() has been called (unless you only use it in the same compilation unit in which it was defined).