首页 文章

std :: string_view究竟比const std :: string&更快?

提问于
浏览
162

std::string_view已经达到了C 17,并且广泛建议使用它而不是 const std::string& .

其中一个原因是性能 .

当有人用作参数类型时,有人可以解释 exactly std::string_view /会比 const std::string& 快吗? (假设在被叫方中没有副本)

5 回答

  • 39

    std::string_view 在少数情况下更快 .

    首先, std::string const& 要求数据在 std::string 中,而不是原始C数组,由C API返回的 char const* ,由某些反序列化引擎生成的 std::vector<char> 等 . 避免的格式转换避免了复制字节,并且(如果是字符串)对于特定的 std::string 实现,它比SBO¹更长)避免了内存分配 .

    void foo( std::string_view bob ) {
      std::cout << bob << "\n";
    }
    int main(int argc, char const*const* argv) {
      foo( "This is a string long enough to avoid the std::string SBO" );
      if (argc > 1)
        foo( argv[1] );
    }
    

    string_view 情况下没有进行任何分配,但是如果 foo 采用了 std::string const& 而不是 string_view .

    第二个非常重要的原因是它允许在没有副本的情况下处理子字符串 . 假设您正在解析一个2千兆字节的json字符串(!)² . 如果将其解析为 std::string ,则每个此类解析节点存储节点的名称或值,将原始数据从2 gb字符串复制到本地节点 .

    相反,如果将其解析为 std::string_view s,则节点将引用原始数据 . 这可以在解析期间节省数百万个分配并减少内存需求 .

    你可以获得的加速非常荒谬 .

    这是一个极端的情况,但其他"get a substring and work with it"案例也可以用 string_view 产生不错的加速 .

    决定的一个重要部分是使用 std::string_view 丢失的内容 . 它并不多,但它是一些东西 .

    你失去了隐式的空终止,这就是它 . 因此,如果将相同的字符串传递给3个函数,所有这些函数都需要空终止符,则转换为 std::string 一次可能是明智的 . 因此,如果您的代码已知需要一个空终止符,并且您不希望从C风格的源缓冲区等提供字符串,则可能需要 std::string const& . 否则拿一个 std::string_view .

    如果 std::string_view 有一个标志,表明它是否为空终止(或更高级的东西),它甚至会删除使用 std::string const& 的最后一个原因 .

    有一种情况是 std::string 没有 const& 优于 std::string_view . 如果您需要在通话后无限期拥有该字符串的副本,则取值按钮是有效的 . 您'll either be in the SBO case (and no allocations, just a few character copies to duplicate it), or you'll能够将堆分配的缓冲区移动到本地 std::string . 有两个重载 std::string&&std::string_view 可能会更快,但只是轻微的,它会导致适度的代码膨胀(这可能会花费你所有的速度增益) .


    ¹小缓冲区优化

    ²实际使用案例 .

  • 164

    string_view提高性能的一种方法是它允许轻松删除前缀和后缀 . 在引擎盖下,string_view只需将前缀大小添加到指向某个字符串缓冲区的指针,或者从字节计数器中减去后缀大小,这通常很快 . 另一方面,当你执行像substr这样的操作时,std :: string必须复制它的字节(这样你就会获得一个拥有其缓冲区的新字符串,但在很多情况下你只想获得原始字符串的一部分而不复制) . 例:

    std::string str{"foobar"};
    auto bar = str.substr(3);
    assert(bar == "bar");
    

    使用std :: string_view:

    std::string str{"foobar"};
    std::string_view bar{str.c_str(), str.size()};
    bar.remove_prefix(3);
    assert(bar == "bar");
    

    更新:

    我写了一个非常简单的基准来添加一些实数 . 我使用了很棒的google benchmark library . 基准功能是:

    string remove_prefix(const string &str) {
      return str.substr(3);
    }
    string_view remove_prefix(string_view str) {
      str.remove_prefix(3);
      return str;
    }
    static void BM_remove_prefix_string(benchmark::State& state) {                
      std::string example{"asfaghdfgsghasfasg3423rfgasdg"};
      while (state.KeepRunning()) {
        auto res = remove_prefix(example);
        // auto res = remove_prefix(string_view(example)); for string_view
        if (res != "aghdfgsghasfasg3423rfgasdg") {
          throw std::runtime_error("bad op");
        }
      }
    }
    // BM_remove_prefix_string_view is similar, I skipped it to keep the post short
    

    结果

    (x86_64 linux,gcc 6.2,“ -O3 -DNDEBUG ”):

    Benchmark                             Time           CPU Iterations
    -------------------------------------------------------------------
    BM_remove_prefix_string              90 ns         90 ns    7740626
    BM_remove_prefix_string_view          6 ns          6 ns  120468514
    
  • 43

    主要有两个原因:

    • string_view 是现有缓冲区中的片,它不需要内存分配

    • string_view 按值传递,而不是按引用传递


    切片的优点是多个:

    • 您可以在不分配新缓冲区的情况下将其与 char const*char[] 一起使用

    • 您可以将多个切片和子切片放入现有缓冲区而无需分配

    • substring是O(1),而不是O(N)

    • ......

    全面更好,更稳定的表现 .


    传递值也比通过引用传递优势,因为别名 .

    具体来说,如果您有 std::string const& 参数,则无法保证不会修改引用字符串 . 因此,编译器必须在每次调用后重新获取字符串的内容为opaque方法(指向数据,长度,...) .

    另一方面,当传递 string_view by值时,编译器可以静态地确定没有其他代码可以修改堆栈(或寄存器)中现在的长度和数据指针 . 因此,它可以跨函数调用"cache" .

  • 5

    它可以做的一件事是避免在从空终止字符串的隐式转换的情况下构造 std::string 对象:

    void foo(const std::string& s);
    
    ...
    
    foo("hello, world!"); // std::string object created, possible dynamic allocation.
    char msg[] = "good morning!";
    foo(msg); // std::string object created, possible dynamic allocation.
    
  • 36

    std::string_view 基本上只是 const char* 的包装器 . 并且传递 const char* 意味着与传递 const string* (或 const string& )相比,系统中将少一个指针,因为 string* 意味着:

    string* -> char* -> char[]
               |   string    |
    

    显然,为了传递const参数的第一个指针是多余的 .

    附:尽管如此, std::string_viewconst char* 之间的一个缺点是,string_views不需要以空值终止(它们具有内置大小),这允许对较长字符串进行随机就地拼接 .

相关问题