Memcpy takes the same time as memset

前端 未结 2 1621
北海茫月
北海茫月 2021-01-07 04:44

I want to measure memory bandwidth using memcpy. I modified the code from this answer:why vectorizing the loop does not have performance improvement which used

2条回答
  •  情歌与酒
    2021-01-07 05:22

    Your b array probably was not written after mmap-ing (huge allocation requests with malloc/calloc are usually converted into mmap). And whole array was mmaped to single read-only "zero page" (part of COW mechanism). Reading zeroes from single page is faster than reading from many pages, as single page will be kept in the cache and in TLB. This explains why test before memset(0) was faster:

    This outputs. 9.472976 12.728426

    However, if I do memset(b,1,LEN) in main after calloc (see below) then it outputs: 12.5 12.5

    And more about gcc's malloc+memset / calloc+memset optimization into calloc (expanded from my comment)

    //GCC optimizes memset(b,0,LEN) away after calloc but Clang does not.
    

    This optimization was proposed in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57742 (tree-optimization PR57742) at 2013-06-27 by Marc Glisse (https://stackoverflow.com/users/1918193?) as planned for 4.9/5.0 version of GCC:

    memset(malloc(n),0,n) -> calloc(n,1)

    calloc can sometimes be significantly faster than malloc+bzero because it has special knowledge that some memory is already zero. When other optimizations simplify some code to malloc+memset(0), it would thus be nice to replace it with calloc. Sadly, I don't think there is a way to do a similar optimization in C++ with new, which is where such code most easily appears (creating std::vector(10000) for instance). And there would also be the complication there that the size of the memset would be a bit smaller than that of the malloc (using calloc would still be fine, but it gets harder to know if it is an improvement).

    Implemented at 2014-06-24 (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57742#c15) - https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=211956 (also https://patchwork.ozlabs.org/patch/325357/)

    • tree-ssa-strlen.c ... (handle_builtin_malloc, handle_builtin_memset): New functions.

    The current code in gcc/tree-ssa-strlen.c https://github.com/gcc-mirror/gcc/blob/7a31ada4c400351a35ab65f8dc0357e7c88805d5/gcc/tree-ssa-strlen.c#L1889 - if memset(0) get pointer from malloc or calloc, it will convert malloc into calloc and then memset(0) will be removed:

    /* Handle a call to memset.
       After a call to calloc, memset(,0,) is unnecessary.
       memset(malloc(n),0,n) is calloc(n,1).  */
    static bool
    handle_builtin_memset (gimple_stmt_iterator *gsi)
     ...
      if (code1 == BUILT_IN_CALLOC)
        /* Not touching stmt1 */ ;
      else if (code1 == BUILT_IN_MALLOC
           && operand_equal_p (gimple_call_arg (stmt1, 0), size, 0))
        {
          gimple_stmt_iterator gsi1 = gsi_for_stmt (stmt1);
          update_gimple_call (&gsi1, builtin_decl_implicit (BUILT_IN_CALLOC), 2,
                  size, build_one_cst (size_type_node));
          si1->length = build_int_cst (size_type_node, 0);
          si1->stmt = gsi_stmt (gsi1);
        }
    

    This was discussed in gcc-patches mailing list in Mar 1, 2014 - Jul 15, 2014 with subject "calloc = malloc + memset"

    • https://gcc.gnu.org/ml/gcc-patches/2014-02/msg01693.html
    • https://gcc.gnu.org/ml/gcc-patches/2014-03/threads.html#00009
    • https://gcc.gnu.org/ml/gcc-patches/2014-04/threads.html#00817
    • https://gcc.gnu.org/ml/gcc-patches/2014-05/msg01392.html
    • https://gcc.gnu.org/ml/gcc-patches/2014-06/threads.html#00234
    • https://gcc.gnu.org/ml/gcc-patches/2014-07/threads.html#01059

    with notable comment from Andi Kleen (http://halobates.de/blog/, https://github.com/andikleen): https://gcc.gnu.org/ml/gcc-patches/2014-06/msg01818.html

    FWIW i believe the transformation will break a large variety of micro benchmarks.

    calloc internally knows that memory fresh from the OS is zeroed. But the memory may not be faulted in yet.

    memset always faults in the memory.

    So if you have some test like

       buf = malloc(...)
       memset(buf, ...) 
       start = get_time();
       ... do something with buf
       end = get_time()
    

    Now the times will be completely off because the measured times includes the page faults.

    Marc replied "Good point. I guess working around compiler optimizations is part of the game for micro benchmarks, and their authors would be disappointed if the compiler didn't mess it up regularly in new and entertaining ways ;-)" and Andi asked: "I would prefer to not do it. I'm not sure it has a lot of benefit. If you want to keep it please make sure there is an easy way to turn it off."

    Marc shows how to turn this optimization off: https://gcc.gnu.org/ml/gcc-patches/2014-06/msg01834.html

    Any of these flags works:

    • -fdisable-tree-strlen
    • -fno-builtin-malloc
    • -fno-builtin-memset (assuming you wrote 'memset' explicitly in your code)
    • -fno-builtin
    • -ffreestanding
    • -O1
    • -Os

    In the code, you can hide that the pointer passed to memset is the one returned by malloc by storing it in a volatile variable, or any other trick to hide from the compiler that we are doing memset(malloc(n),0,n).

提交回复
热议问题