EDIT: Code is available here: https://github.com/acmorrow/stringview_param
I've created some example code which appears to demonstrate that pass-by-value for string_view like objects results in better code for both callers and function definitions on at least one platform.
First, we define a fake string_view class (I didn't have the real thing handy) in string_view.h
:
#pragma once
#include <string>
class string_view {
public:
string_view()
: _data(nullptr)
, _len(0) {
}
string_view(const char* data)
: _data(data)
, _len(strlen(data)) {
}
string_view(const std::string& data)
: _data(data.data())
, _len(data.length()) {
}
const char* data() const {
return _data;
}
std::size_t len() const {
return _len;
}
private:
const char* _data;
size_t _len;
};
Now, lets define some functions that consume a string_view, either by value or by reference. Here are the signatures in example.hpp
:
#pragma once
class string_view;
void __attribute__((visibility("default"))) use_as_value(string_view view);
void __attribute__((visibility("default"))) use_as_const_ref(const string_view& view);
The bodies of these functions are defined as follows, in example.cpp
:
#include "example.hpp"
#include <cstdio>
#include "do_something_else.hpp"
#include "string_view.hpp"
void use_as_value(string_view view) {
printf("%ld %ld %zu\n", strchr(view.data(), 'a') - view.data(), view.len(), strlen(view.data()));
do_something_else();
printf("%ld %ld %zu\n", strchr(view.data(), 'a') - view.data(), view.len(), strlen(view.data()));
}
void use_as_const_ref(const string_view& view) {
printf("%ld %ld %zu\n", strchr(view.data(), 'a') - view.data(), view.len(), strlen(view.data()));
do_something_else();
printf("%ld %ld %zu\n", strchr(view.data(), 'a') - view.data(), view.len(), strlen(view.data()));
}
The do_something_else
function is here is a stand-in for arbitrary calls to functions that the compiler does not have insight about (e.g. functions from other dynamic objects, etc.). The declaration is in do_something_else.hpp
:
#pragma once
void __attribute__((visibility("default"))) do_something_else();
And the trivial definition is in do_something_else.cpp
:
#include "do_something_else.hpp"
#include <cstdio>
void do_something_else() {
std::printf("Doing something\n");
}
We now compile do_something_else.cpp and example.cpp into individual dynamic libraries. Compiler here is XCode 6 clang on OS X Yosemite 10.10.1:
clang++ -mmacosx-version-min=10.10 --stdlib=libc++ -O3 -flto -march=native -fvisibility-inlines-hidden -fvisibility=hidden --std=c++11 ./do_something_else.cpp -fPIC -shared -o libdo_something_else.dylib
clang++ -mmacosx-version-min=10.10 --stdlib=libc++ -O3 -flto -march=native -fvisibility-inlines-hidden -fvisibility=hidden --std=c++11 ./example.cpp -fPIC -shared -o libexample.dylib -L. -ldo_something_else
Now, we disassemble libexample.dylib:
> otool -tVq ./libexample.dylib
./libexample.dylib:
(__TEXT,__text) section
__Z12use_as_value11string_view:
0000000000000d80 pushq %rbp
0000000000000d81 movq %rsp, %rbp
0000000000000d84 pushq %r15
0000000000000d86 pushq %r14
0000000000000d88 pushq %r12
0000000000000d8a pushq %rbx
0000000000000d8b movq %rsi, %r14
0000000000000d8e movq %rdi, %rbx
0000000000000d91 movl $0x61, %esi
0000000000000d96 callq 0xf42 ## symbol stub for: _strchr
0000000000000d9b movq %rax, %r15
0000000000000d9e subq %rbx, %r15
0000000000000da1 movq %rbx, %rdi
0000000000000da4 callq 0xf48 ## symbol stub for: _strlen
0000000000000da9 movq %rax, %rcx
0000000000000dac leaq 0x1d5(%rip), %r12 ## literal pool for: "%ld %ld %zu\n"
0000000000000db3 xorl %eax, %eax
0000000000000db5 movq %r12, %rdi
0000000000000db8 movq %r15, %rsi
0000000000000dbb movq %r14, %rdx
0000000000000dbe callq 0xf3c ## symbol stub for: _printf
0000000000000dc3 callq 0xf36 ## symbol stub for: __Z17do_something_elsev
0000000000000dc8 movl $0x61, %esi
0000000000000dcd movq %rbx, %rdi
0000000000000dd0 callq 0xf42 ## symbol stub for: _strchr
0000000000000dd5 movq %rax, %r15
0000000000000dd8 subq %rbx, %r15
0000000000000ddb movq %rbx, %rdi
0000000000000dde callq 0xf48 ## symbol stub for: _strlen
0000000000000de3 movq %rax, %rcx
0000000000000de6 xorl %eax, %eax
0000000000000de8 movq %r12, %rdi
0000000000000deb movq %r15, %rsi
0000000000000dee movq %r14, %rdx
0000000000000df1 popq %rbx
0000000000000df2 popq %r12
0000000000000df4 popq %r14
0000000000000df6 popq %r15
0000000000000df8 popq %rbp
0000000000000df9 jmp 0xf3c ## symbol stub for: _printf
0000000000000dfe nop
__Z16use_as_const_refRK11string_view:
0000000000000e00 pushq %rbp
0000000000000e01 movq %rsp, %rbp
0000000000000e04 pushq %r15
0000000000000e06 pushq %r14
0000000000000e08 pushq %r13
0000000000000e0a pushq %r12
0000000000000e0c pushq %rbx
0000000000000e0d pushq %rax
0000000000000e0e movq %rdi, %r14
0000000000000e11 movq (%r14), %rbx
0000000000000e14 movl $0x61, %esi
0000000000000e19 movq %rbx, %rdi
0000000000000e1c callq 0xf42 ## symbol stub for: _strchr
0000000000000e21 movq %rax, %r15
0000000000000e24 subq %rbx, %r15
0000000000000e27 movq 0x8(%r14), %r12
0000000000000e2b movq %rbx, %rdi
0000000000000e2e callq 0xf48 ## symbol stub for: _strlen
0000000000000e33 movq %rax, %rcx
0000000000000e36 leaq 0x14b(%rip), %r13 ## literal pool for: "%ld %ld %zu\n"
0000000000000e3d xorl %eax, %eax
0000000000000e3f movq %r13, %rdi
0000000000000e42 movq %r15, %rsi
0000000000000e45 movq %r12, %rdx
0000000000000e48 callq 0xf3c ## symbol stub for: _printf
0000000000000e4d callq 0xf36 ## symbol stub for: __Z17do_something_elsev
0000000000000e52 movq (%r14), %rbx
0000000000000e55 movl $0x61, %esi
0000000000000e5a movq %rbx, %rdi
0000000000000e5d callq 0xf42 ## symbol stub for: _strchr
0000000000000e62 movq %rax, %r15
0000000000000e65 subq %rbx, %r15
0000000000000e68 movq 0x8(%r14), %r14
0000000000000e6c movq %rbx, %rdi
0000000000000e6f callq 0xf48 ## symbol stub for: _strlen
0000000000000e74 movq %rax, %rcx
0000000000000e77 xorl %eax, %eax
0000000000000e79 movq %r13, %rdi
0000000000000e7c movq %r15, %rsi
0000000000000e7f movq %r14, %rdx
0000000000000e82 addq $0x8, %rsp
0000000000000e86 popq %rbx
0000000000000e87 popq %r12
0000000000000e89 popq %r13
0000000000000e8b popq %r14
0000000000000e8d popq %r15
0000000000000e8f popq %rbp
0000000000000e90 jmp 0xf3c ## symbol stub for: _printf
0000000000000e95 nopw %cs:(%rax,%rax)
Interestingly, the by-value version is several instructions shorter. But that is only the function bodies. What about callers?
We will define some functions that invoke these two overloads, forwarding a const std::string&
, in example_users.hpp
:
#pragma once
#include <string>
void __attribute__((visibility("default"))) forward_to_use_as_value(const std::string& str);
void __attribute__((visibility("default"))) forward_to_use_as_const_ref(const std::string& str);
And define them in example_users.cpp
:
#include "example_users.hpp"
#include "example.hpp"
#include "string_view.hpp"
void forward_to_use_as_value(const std::string& str) {
use_as_value(str);
}
void forward_to_use_as_const_ref(const std::string& str) {
use_as_const_ref(str);
}
Again, we compile example_users.cpp
to a shared library:
clang++ -mmacosx-version-min=10.10 --stdlib=libc++ -O3 -flto -march=native -fvisibility-inlines-hidden -fvisibility=hidden --std=c++11 ./example_users.cpp -fPIC -shared -o libexample_users.dylib -L. -lexample
And, again, we look at the generated code:
> otool -tVq ./libexample_users.dylib
./libexample_users.dylib:
(__TEXT,__text) section
__Z23forward_to_use_as_valueRKNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE:
0000000000000e70 pushq %rbp
0000000000000e71 movq %rsp, %rbp
0000000000000e74 movzbl (%rdi), %esi
0000000000000e77 testb $0x1, %sil
0000000000000e7b je 0xe8b
0000000000000e7d movq 0x8(%rdi), %rsi
0000000000000e81 movq 0x10(%rdi), %rdi
0000000000000e85 popq %rbp
0000000000000e86 jmp 0xf60 ## symbol stub for: __Z12use_as_value11string_view
0000000000000e8b incq %rdi
0000000000000e8e shrq %rsi
0000000000000e91 popq %rbp
0000000000000e92 jmp 0xf60 ## symbol stub for: __Z12use_as_value11string_view
0000000000000e97 nopw (%rax,%rax)
__Z27forward_to_use_as_const_refRKNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE:
0000000000000ea0 pushq %rbp
0000000000000ea1 movq %rsp, %rbp
0000000000000ea4 subq $0x10, %rsp
0000000000000ea8 movzbl (%rdi), %eax
0000000000000eab testb $0x1, %al
0000000000000ead je 0xebd
0000000000000eaf movq 0x10(%rdi), %rax
0000000000000eb3 movq %rax, -0x10(%rbp)
0000000000000eb7 movq 0x8(%rdi), %rax
0000000000000ebb jmp 0xec7
0000000000000ebd incq %rdi
0000000000000ec0 movq %rdi, -0x10(%rbp)
0000000000000ec4 shrq %rax
0000000000000ec7 movq %rax, -0x8(%rbp)
0000000000000ecb leaq -0x10(%rbp), %rdi
0000000000000ecf callq 0xf66 ## symbol stub for: __Z16use_as_const_refRK11string_view
0000000000000ed4 addq $0x10, %rsp
0000000000000ed8 popq %rbp
0000000000000ed9 retq
0000000000000eda nopw (%rax,%rax)
And, again, the by-value version is several instructions shorter.
It appears to me that, at least by the coarse metric of instruction count, that the by-value version produces better code for both callers and for generated function bodies.
I'm of course open to suggestions to how to improve this test. Obviously a next step would be to refactor this into something where I could benchmark it meaningfully. I will try to do that soon.
I will post the example code to github with some sort of build script so others can test on their systems.
But based on the discussion above, and the results of inspecting the generated code, my conclusion is that pass-by-value is the way to go for view types.