草庐IT

c++ - Linux 上的 memcpy 性能不佳

coder 2023-04-26 原文

我们最近购买了一些新服务器,但 memcpy 性能不佳。与我们的笔记本电脑相比,服务器上的 memcpy 性能要慢 3 倍。

服务器规范

  • 机箱和主板:SUPER MICRO 1027GR-TRF
  • CPU:2x Intel Xeon E5-2680 @ 2.70 Ghz
  • 内存:8x 16GB DDR3 1600MHz

  • 编辑:我也在另一台规范稍高的服务器上进行测试,看到的结果与上述服务器相同

    服务器 2 规范
  • 机箱和主板:SUPER MICRO 10227GR-TRFT
  • CPU:2x Intel Xeon E5-2650 v2 @ 2.6 Ghz
  • 内存:8x 16GB DDR3 1866MHz

  • 笔记本电脑规范
  • 机箱:联想W530
  • CPU:1x 英特尔酷睿 i7 i7-3720QM @ 2.6Ghz
  • 内存:4x 4GB DDR3 1600MHz

  • 操作系统
    $ cat /etc/redhat-release
    Scientific Linux release 6.5 (Carbon) 
    $ uname -a                      
    Linux r113 2.6.32-431.1.2.el6.x86_64 #1 SMP Thu Dec 12 13:59:19 CST 2013 x86_64 x86_64 x86_64 GNU/Linux
    

    编译器(在所有系统上)
    $ gcc --version
    gcc (GCC) 4.6.1
    

    还根据@stefan 的建议使用 gcc 4.8.2 进行了测试。编译器之间没有性能差异。

    测试代码
    下面的测试代码是一个固定测试,用于复制我在生产代码中看到的问题。我知道这个基准测试很简单,但它能够利用和识别我们的问题。该代码在它们之间创建了两个 1GB 缓冲区和 memcpy,为 memcpy 调用计时。您可以使用以下命令在命令行上指定备用缓冲区大小:./big_memcpy_test [SIZE_BYTES]
    #include <chrono>
    #include <cstring>
    #include <iostream>
    #include <cstdint>
    
    class Timer
    {
     public:
      Timer()
          : mStart(),
            mStop()
      {
        update();
      }
    
      void update()
      {
        mStart = std::chrono::high_resolution_clock::now();
        mStop  = mStart;
      }
    
      double elapsedMs()
      {
        mStop = std::chrono::high_resolution_clock::now();
        std::chrono::milliseconds elapsed_ms =
            std::chrono::duration_cast<std::chrono::milliseconds>(mStop - mStart);
        return elapsed_ms.count();
      }
    
     private:
      std::chrono::high_resolution_clock::time_point mStart;
      std::chrono::high_resolution_clock::time_point mStop;
    };
    
    std::string formatBytes(std::uint64_t bytes)
    {
      static const int num_suffix = 5;
      static const char* suffix[num_suffix] = { "B", "KB", "MB", "GB", "TB" };
      double dbl_s_byte = bytes;
      int i = 0;
      for (; (int)(bytes / 1024.) > 0 && i < num_suffix;
           ++i, bytes /= 1024.)
      {
        dbl_s_byte = bytes / 1024.0;
      }
    
      const int buf_len = 64;
      char buf[buf_len];
    
      // use snprintf so there is no buffer overrun
      int res = snprintf(buf, buf_len,"%0.2f%s", dbl_s_byte, suffix[i]);
    
      // snprintf returns number of characters that would have been written if n had
      //       been sufficiently large, not counting the terminating null character.
      //       if an encoding error occurs, a negative number is returned.
      if (res >= 0)
      {
        return std::string(buf);
      }
      return std::string();
    }
    
    void doMemmove(void* pDest, const void* pSource, std::size_t sizeBytes)
    {
      memmove(pDest, pSource, sizeBytes);
    }
    
    int main(int argc, char* argv[])
    {
      std::uint64_t SIZE_BYTES = 1073741824; // 1GB
    
      if (argc > 1)
      {
        SIZE_BYTES = std::stoull(argv[1]);
        std::cout << "Using buffer size from command line: " << formatBytes(SIZE_BYTES)
                  << std::endl;
      }
      else
      {
        std::cout << "To specify a custom buffer size: big_memcpy_test [SIZE_BYTES] \n"
                  << "Using built in buffer size: " << formatBytes(SIZE_BYTES)
                  << std::endl;
      }
    
    
      // big array to use for testing
      char* p_big_array = NULL;
    
      /////////////
      // malloc 
      {
        Timer timer;
    
        p_big_array = (char*)malloc(SIZE_BYTES * sizeof(char));
        if (p_big_array == NULL)
        {
          std::cerr << "ERROR: malloc of " << SIZE_BYTES << " returned NULL!"
                    << std::endl;
          return 1;
        }
    
        std::cout << "malloc for " << formatBytes(SIZE_BYTES) << " took "
                  << timer.elapsedMs() << "ms"
                  << std::endl;
      }
    
      /////////////
      // memset
      {
        Timer timer;
    
        // set all data in p_big_array to 0
        memset(p_big_array, 0xF, SIZE_BYTES * sizeof(char));
    
        double elapsed_ms = timer.elapsedMs();
        std::cout << "memset for " << formatBytes(SIZE_BYTES) << " took "
                  << elapsed_ms << "ms "
                  << "(" << formatBytes(SIZE_BYTES / (elapsed_ms / 1.0e3)) << " bytes/sec)"
                  << std::endl;
      }
    
      /////////////
      // memcpy 
      {
        char* p_dest_array = (char*)malloc(SIZE_BYTES);
        if (p_dest_array == NULL)
        {
          std::cerr << "ERROR: malloc of " << SIZE_BYTES << " for memcpy test"
                    << " returned NULL!"
                    << std::endl;
          return 1;
        }
        memset(p_dest_array, 0xF, SIZE_BYTES * sizeof(char));
    
        // time only the memcpy FROM p_big_array TO p_dest_array
        Timer timer;
    
        memcpy(p_dest_array, p_big_array, SIZE_BYTES * sizeof(char));
    
        double elapsed_ms = timer.elapsedMs();
        std::cout << "memcpy for " << formatBytes(SIZE_BYTES) << " took "
                  << elapsed_ms << "ms "
                  << "(" << formatBytes(SIZE_BYTES / (elapsed_ms / 1.0e3)) << " bytes/sec)"
                  << std::endl;
    
        // cleanup p_dest_array
        free(p_dest_array);
        p_dest_array = NULL;
      }
    
      /////////////
      // memmove
      {
        char* p_dest_array = (char*)malloc(SIZE_BYTES);
        if (p_dest_array == NULL)
        {
          std::cerr << "ERROR: malloc of " << SIZE_BYTES << " for memmove test"
                    << " returned NULL!"
                    << std::endl;
          return 1;
        }
        memset(p_dest_array, 0xF, SIZE_BYTES * sizeof(char));
    
        // time only the memmove FROM p_big_array TO p_dest_array
        Timer timer;
    
        // memmove(p_dest_array, p_big_array, SIZE_BYTES * sizeof(char));
        doMemmove(p_dest_array, p_big_array, SIZE_BYTES * sizeof(char));
    
        double elapsed_ms = timer.elapsedMs();
        std::cout << "memmove for " << formatBytes(SIZE_BYTES) << " took "
                  << elapsed_ms << "ms "
                  << "(" << formatBytes(SIZE_BYTES / (elapsed_ms / 1.0e3)) << " bytes/sec)"
                  << std::endl;
    
        // cleanup p_dest_array
        free(p_dest_array);
        p_dest_array = NULL;
      }
    
    
      // cleanup
      free(p_big_array);
      p_big_array = NULL;
    
      return 0;
    }
    

    要构建的 CMake 文件
    project(big_memcpy_test)
    cmake_minimum_required(VERSION 2.4.0)
    
    include_directories(${CMAKE_CURRENT_SOURCE_DIR})
    
    # create verbose makefiles that show each command line as it is issued
    set( CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "Verbose" FORCE )
    # release mode
    set( CMAKE_BUILD_TYPE Release )
    # grab in CXXFLAGS environment variable and append C++11 and -Wall options
    set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall -march=native -mtune=native" )
    message( INFO "CMAKE_CXX_FLAGS = ${CMAKE_CXX_FLAGS}" )
    
    # sources to build
    set(big_memcpy_test_SRCS
      main.cpp
    )
    
    # create an executable file named "big_memcpy_test" from
    # the source files in the variable "big_memcpy_test_SRCS".
    add_executable(big_memcpy_test ${big_memcpy_test_SRCS})
    

    测试结果
    Buffer Size: 1GB | malloc (ms) | memset (ms) | memcpy (ms) | NUMA nodes (numactl --hardware)
    ---------------------------------------------------------------------------------------------
    Laptop 1         | 0           | 127         | 113         | 1
    Laptop 2         | 0           | 180         | 120         | 1
    Server 1         | 0           | 306         | 301         | 2
    Server 2         | 0           | 352         | 325         | 2
    

    正如您所看到的,我们服务器上的 memcpys 和 memset 比我们笔记本电脑上的 memcpys 和 memset 慢得多。

    不同的缓冲区大小

    我尝试过从 100MB 到 5GB 的缓冲区,结果都相似(服务器比笔记本电脑慢)

    NUMA 亲和力

    我读到有人在使用 NUMA 时遇到性能问题,因此我尝试使用 numactl 设置 CPU 和内存关联,但结果保持不变。

    服务器 NUMA 硬件
    $ numactl --hardware                                                            
    available: 2 nodes (0-1)                                                                     
    node 0 cpus: 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23                                         
    node 0 size: 65501 MB                                                                        
    node 0 free: 62608 MB                                                                        
    node 1 cpus: 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31                                   
    node 1 size: 65536 MB                                                                        
    node 1 free: 63837 MB                                                                        
    node distances:                                                                              
    node   0   1                                                                                 
      0:  10  21                                                                                 
      1:  21  10 
    

    笔记本电脑 NUMA 硬件
    $ numactl --hardware
    available: 1 nodes (0)
    node 0 cpus: 0 1 2 3 4 5 6 7
    node 0 size: 16018 MB
    node 0 free: 6622 MB
    node distances:
    node   0 
      0:  10
    

    设置 NUMA 亲和力
    $ numactl --cpunodebind=0 --membind=0 ./big_memcpy_test
    

    非常感谢任何帮助解决此问题。

    编辑:GCC 选项

    根据评论,我尝试使用不同的 GCC 选项进行编译:

    编译时将 -march 和 -mtune 设置为 native
    g++ -std=c++0x -Wall -march=native -mtune=native -O3 -DNDEBUG -o big_memcpy_test main.cpp 
    

    结果:完全相同的性能(没有改进)

    使用 -O2 而不是 -O3 编译
    g++ -std=c++0x -Wall -march=native -mtune=native -O2 -DNDEBUG -o big_memcpy_test main.cpp
    

    结果:完全相同的性能(没有改进)

    编辑:将 memset 更改为写入 0xF 而不是 0 以避免 NULL 页面(@SteveCox)

    使用 0 以外的值进行 memsetting 时没有改进(在这种情况下使用 0xF)。

    编辑:Cachebench 结果

    为了排除我的测试程序过于简单,我下载了一个真正的基准测试程序 LLCacheBench ( http://icl.cs.utk.edu/projects/llcbench/cachebench.html )

    我分别在每台机器上构建了基准测试以避免架构问题。下面是我的结果。



    请注意,较大缓冲区大小的性能差异非常大。最后测试的大小 (16777216) 在笔记本电脑上以 18849.29 MB/秒的速度执行,在服务器上以 6710.40 的速度执行。这大约是性能的 3 倍差异。您还可以注意到服务器的性能下降比笔记本电脑上的要陡峭得多。

    编辑:memmove() 比服务器上的 memcpy() 快 2 倍

    基于一些实验,我尝试在我的测试用例中使用 memmove() 而不是 memcpy() 并发现服务器上的性能提高了 2 倍。笔记本电脑上的 Memmove() 运行速度比 memcpy() 慢,但奇怪的是,它的运行速度与服务器上的 memmove() 速度相同。这就引出了一个问题,为什么 memcpy 这么慢?

    更新代码以测试 memmove 和 memcpy。我不得不将 memmove() 包装在一个函数中,因为如果我让它内联 GCC 优化它并执行与 memcpy() 完全相同的操作(我假设 gcc 将它优化为 memcpy,因为它知道位置没有重叠)。

    更新结果
    Buffer Size: 1GB | malloc (ms) | memset (ms) | memcpy (ms) | memmove() | NUMA nodes (numactl --hardware)
    ---------------------------------------------------------------------------------------------------------
    Laptop 1         | 0           | 127         | 113         | 161       | 1
    Laptop 2         | 0           | 180         | 120         | 160       | 1
    Server 1         | 0           | 306         | 301         | 159       | 2
    Server 2         | 0           | 352         | 325         | 159       | 2
    

    编辑:Naive Memcpy

    根据@Salgar 的建议,我实现了自己的天真的 memcpy 函数并对其进行了测试。

    朴素的 Memcpy 源
    void naiveMemcpy(void* pDest, const void* pSource, std::size_t sizeBytes)
    {
      char* p_dest = (char*)pDest;
      const char* p_source = (const char*)pSource;
      for (std::size_t i = 0; i < sizeBytes; ++i)
      {
        *p_dest++ = *p_source++;
      }
    }
    

    与 memcpy() 相比的朴素 Memcpy 结果
    Buffer Size: 1GB | memcpy (ms) | memmove(ms) | naiveMemcpy()
    ------------------------------------------------------------
    Laptop 1         | 113         | 161         | 160
    Server 1         | 301         | 159         | 159
    Server 2         | 325         | 159         | 159
    

    编辑:程序集输出

    简单的memcpy源码
    #include <cstring>
    #include <cstdlib>
    
    int main(int argc, char* argv[])
    {
      size_t SIZE_BYTES = 1073741824; // 1GB
    
      char* p_big_array  = (char*)malloc(SIZE_BYTES * sizeof(char));
      char* p_dest_array = (char*)malloc(SIZE_BYTES * sizeof(char));
    
      memset(p_big_array,  0xA, SIZE_BYTES * sizeof(char));
      memset(p_dest_array, 0xF, SIZE_BYTES * sizeof(char));
    
      memcpy(p_dest_array, p_big_array, SIZE_BYTES * sizeof(char));
    
      free(p_dest_array);
      free(p_big_array);
    
      return 0;
    }
    

    组装输出:这在服务器和笔记本电脑上完全相同。我正在节省空间而不是同时粘贴两者。
            .file   "main_memcpy.cpp"
            .section        .text.startup,"ax",@progbits
            .p2align 4,,15
            .globl  main
            .type   main, @function
    main:
    .LFB25:
            .cfi_startproc
            pushq   %rbp
            .cfi_def_cfa_offset 16
            .cfi_offset 6, -16
            movl    $1073741824, %edi
            pushq   %rbx
            .cfi_def_cfa_offset 24
            .cfi_offset 3, -24
            subq    $8, %rsp
            .cfi_def_cfa_offset 32
            call    malloc
            movl    $1073741824, %edi
            movq    %rax, %rbx
            call    malloc
            movl    $1073741824, %edx
            movq    %rax, %rbp
            movl    $10, %esi
            movq    %rbx, %rdi
            call    memset
            movl    $1073741824, %edx
            movl    $15, %esi
            movq    %rbp, %rdi
            call    memset
            movl    $1073741824, %edx
            movq    %rbx, %rsi
            movq    %rbp, %rdi
            call    memcpy
            movq    %rbp, %rdi
            call    free
            movq    %rbx, %rdi
            call    free
            addq    $8, %rsp
            .cfi_def_cfa_offset 24
            xorl    %eax, %eax
            popq    %rbx
            .cfi_def_cfa_offset 16
            popq    %rbp
            .cfi_def_cfa_offset 8
            ret
            .cfi_endproc
    .LFE25:
            .size   main, .-main
            .ident  "GCC: (GNU) 4.6.1"
            .section        .note.GNU-stack,"",@progbits
    

    进步!!!! asmlib

    根据@tbenson 的建议,我尝试使用 asmlib 运行memcpy 的版本。我的结果最初很差,但是在将 SetMemcpyCacheLimit() 更改为 1GB(我的缓冲区大小)后,我的运行速度与我的幼稚 for 循环相当!

    坏消息是,memmove 的 asmlib 版本比 glibc 版本慢,它现在以 300 毫秒的速度运行(与 memcpy 的 glibc 版本相当)。奇怪的是,在笔记本电脑上,当我将 SetMemcpyCacheLimit() 设置为大量时,它会损害性能...

    在下面的结果中,标有 SetCache 的行将 SetMemcpyCacheLimit 设置为 1073741824。没有 SetCache 的结果不调用 SetMemcpyCacheLimit()

    使用 asmlib 函数的结果:
    Buffer Size: 1GB  | memcpy (ms) | memmove(ms) | naiveMemcpy()
    ------------------------------------------------------------
    Laptop            | 136         | 132         | 161
    Laptop SetCache   | 182         | 137         | 161
    Server 1          | 305         | 302         | 164
    Server 1 SetCache | 162         | 303         | 164
    Server 2          | 300         | 299         | 166
    Server 2 SetCache | 166         | 301         | 166
    

    开始倾向于缓存问题,但什么会导致这种情况?

    最佳答案

    [我会发表评论,但没有足够的声誉这样做。]

    我有一个类似的系统并看到类似的结果,但可以添加一些数据点:

  • 如果你扭转了你幼稚的方向memcpy (即转换为 *p_dest-- = *p_src-- ),那么您的性能可能比前向性能差得多(对我来说约为 637 毫秒)。 memcpy()发生了变化在 glibc 2.12 中暴露了几个调用 memcpy 的错误在重叠缓冲区( http://lwn.net/Articles/414467/ )上,我相信该问题是由切换到 memcpy 的版本引起的反向操作。因此,向后与向前拷贝可以解释 memcpy()/memmove()差距。
  • 最好不要使用非临时存储。许多优化memcpy()对于大缓冲区(即大于最后一级缓存),实现切换到非临时存储(未缓存)。我测试了 Agner Fog 的 memcpy 版本( http://www.agner.org/optimize/#asmlib ),发现它的速度与 glibc 中的版本大致相同。 .然而,asmlib有一个函数 ( SetMemcpyCacheLimit ) 允许设置使用非临时存储的阈值。将该限制设置为 8GiB(或仅大于 1GiB 缓冲区)以避免非临时存储在我的情况下性能翻倍(时间降至 176 毫秒)。当然,那只匹配了前向幼稚的表现,所以它并不出色。
  • 这些系统上的 BIOS 允许启用/禁用四种不同的硬件预取器(MLC Streamer Prefetcher、MLC Spatial Prefetcher、DCU Streamer Prefetcher 和 DCU IP Prefetcher)。我尝试禁用每个设置,但这样做最多可以保持性能均衡并降低一些设置的性能。
  • 禁用运行平均功率限制 (RAPL) DRAM 模式没有影响。
  • 我可以访问其他运行 Fedora 19 (glibc 2.17) 的 Supermicro 系统。使用 Supermicro X9DRG-HF 主板、Fedora 19 和 Xeon E5-2670 CPU,我看到与上述类似的性能。在运行 Xeon E3-1275 v3 (Haswell) 和 Fedora 19 的 Supermicro X10SLM-F 单插槽板上,我看到 memcpy 为 9.6 GB/s (104 毫秒)。 Haswell 系统上的 RAM 为 DDR3-1600(与其他系统相同)。

  • 更新
  • 我将 CPU 电源管理设置为 Max Performance,并在 BIOS 中禁用了超线程。基于 /proc/cpuinfo ,然后内核的时钟频率为 3 GHz。然而,这奇怪地降低了大约 10% 的内存性能。
  • memtest86+ 4.10 报告主内存的带宽为 9091 MB/s。我找不到这是否对应于读取、写入或复制。
  • STREAM benchmark报告 13422 MB/s 的复制速度,但它们将读取和写入的字节数计为字节,因此如果我们想与上述结果进行比较,这对应于 ~6.5 GB/s。
  • 关于c++ - Linux 上的 memcpy 性能不佳,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22793669/

    有关c++ - Linux 上的 memcpy 性能不佳的更多相关文章

    1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

      我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

    2. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

      我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

    3. ruby-on-rails - openshift 上的 rails 控制台 - 2

      我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

    4. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

      我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

    5. ruby - 使用 `+=` 和 `send` 方法 - 2

      如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

    6. ruby - 如何计算 Liquid 中的变量 +1 - 2

      我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

    7. ruby-on-rails - Ruby - 如何从 ruby​​ 上的 .pfx 文件中提取公钥、rsa 私钥和 CA key - 2

      我有一个.pfx格式的证书,我需要使用ruby​​提取公共(public)、私有(private)和CA证书。使用shell我可以这样做:#ExtractPublicKey(askforpassword)opensslpkcs12-infile.pfx-outfile_public.pem-clcerts-nokeys#ExtractCertificateAuthorityKey(askforpassword)opensslpkcs12-infile.pfx-outfile_ca.pem-cacerts-nokeys#ExtractPrivateKey(askforpassword)o

    8. 带有 attr_accessor 的类上的 Ruby instance_eval - 2

      我了解instance_eval和class_eval之间的基本区别。我在玩弄时发现的是一些涉及attr_accessor的奇怪东西。这是一个例子:A=Class.newA.class_eval{attr_accessor:x}a=A.newa.x="x"a.x=>"x"#...expectedA.instance_eval{attr_accessor:y}A.y="y"=>NoMethodError:undefinedmethod`y='forA:Classa.y="y"=>"y"#WHATTT?这是怎么回事:instance_eval没有访问我们的A类(对象)然后它实际上将它添加到

    9. ruby-on-rails - rails 上的 ruby : radio buttons for collection select - 2

      我有一个集合选择:此方法的单选按钮是什么?谢谢 最佳答案 Rails3中没有这样的助手。在Rails4中,它是collection_radio_buttons. 关于ruby-on-rails-rails上的ruby:radiobuttonsforcollectionselect,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/18525986/

    10. arrays - Ruby 数组 += vs 推送 - 2

      我有一个数组数组,想将元素附加到子数组。+=做我想做的,但我想了解为什么push不做。我期望的行为(并与+=一起工作):b=Array.new(3,[])b[0]+=["apple"]b[1]+=["orange"]b[2]+=["frog"]b=>[["苹果"],["橙子"],["Frog"]]通过推送,我将推送的元素附加到每个子数组(为什么?):a=Array.new(3,[])a[0].push("apple")a[1].push("orange")a[2].push("frog")a=>[[“苹果”、“橙子”、“Frog”]、[“苹果”、“橙子”、“Frog”]、[“苹果”、“

    随机推荐