Why passing a QStringView by value is faster than a reference-to-const?

Viewed 958

I have found this in Qt's Doc:

QStringViews should be passed by value, not by reference-to-const:

and they give the following example:

void myfun1(QStringView sv);        // preferred
void myfun2(const QStringView &sv); // compiles and works, but slower

How is this possible?

2 Answers

QStringView is typically used in high-performance code where creating actual QString objects would be slow due to the memory allocation involved. I optimized QStringView so that it is as performant as handling a (const QChar*, size_t) manually. I even went to the length of having member function calls hand off inline to free functions, passing *this by value. All this is to avoid forcing QStringView objects onto the stack, or, more generally, into memory.

In virtually all C++ compilers, a QStringView object is represented as a pair of CPU registers, and many C++ ABIs (sadly, excluding Windows) support passing such types in CPU registers to functions. If you wrote the member function naïvely, with an implicit this parameter as the address of the object, calling such a function out-of-line would force the compiler to allocate a QStringView object on the stack to be able to pass its address as the first argument of the member function.

There's a second argument for pass-by-value: there are less aliasing problems. As a reference type, QStringView exhibits that problem anyway, but consider std::complex: Take

std::complex &operator*=(std::complex &lhs, const std::complex &rhs);

(template args omitted for brevity). This can be called like this:

std::complex c = 3 + 4i;
c *= c;

If you naïvely implement operator*= as if it was a mathematical function:

auto r = real(), i = imag();
m_real = r * other.real() - i * other.imag();
m_imag = r * other.imag() + i * other.real();

you would have clobbered other.real() after the first line, thus calculating a wrong result (yes, people do write this code in production). Passing the rhs by value makes the problem go away.

They say in the docs that it should be passed by value, because QStringView is not a string itself, it is just kind of an interface (well, that's why it is called "view") giving you the read access to the referenced string. Therefore, most likely the size of QStringView is similar to the size of a reference. According to QT source code, QStringView has just 2 fields:

private:
    qsizetype m_size;
    const storage_type *m_data;

On my Debian9 x64 machine with gcc 6.3 the size of this class is 8 (pointer) + 4 (integer) = 12 bytes. The reference size in my case is 8 bytes, consequently there is only a small difference in terms of data copy on function call. Just remember, that on different machine references might be implemented in another way and might be larger.

While I agree that this kind of interface should be passed by copy (not const ref), I don't understand why do they claim that passing it by const ref is actually SLOWER (dereferencing the variable twice doesn't really have any observable effect in modern applications). I really would like to see some test supporting that statement.

Related