0

Let's say there is some custom std::vector-like container that uses memcpy optimization in it's copy constructor to memcpy trivially constructible objects instead of calling multiple copy constructors.

How can I unit test that the optimization is really applied?

i.e.

struct foo { /* some members */ };
static_assert(std::is_trivially_copyable_v<foo>);

my_vector<foo> v1{ /* some foo objects */ };

my_vector<foo> v2 = v1; // how do I test that single memcpy was called for all foo objects instead of multiple foo copy ctors? or how do I test that none copy ctors were called at all?
wohlstad
  • 12,661
  • 10
  • 26
  • 39
Voivoid
  • 461
  • 3
  • 11
  • You can't. You can only test whether they are eligible for memcpy. Compiler optimizations are not something determined in the c++ language. And adding anything to the copy ctor will cause it to cease being trivially copyable. – doug May 07 '23 at 14:49
  • There is a closely related question here: https://stackoverflow.com/questions/35557041/how-can-i-unit-test-performance-optimisations-in-c – nielsen May 07 '23 at 15:11
  • I believe this isn't a compiler ( or std library ) optimization but a custom data structure optimization. I just want to try to make sure that my container actually uses the optimization correctly – Voivoid May 07 '23 at 16:25

2 Answers2

0

There may be ways of replacing the standard library implementation of memcpy with something that saves data about any copying done (see e.g. How to replace C standard library function ? ), but in general it would be easiest to pass an additinal template parameter in that contains implementations for the copying:

struct default_copier
{
    void memcpy(void* dest, void const* src, size_t size) const noexcept
    {
        std::memcpy(dest, src, size);
    }
};

template<class T, class Copier = default_copier>
class my_vector
{
    [[no_unique_address]] Copier m_copier;

    T m_values [10]{};
public:
    my_vector()
    {
    }

    void fill_first(T const* values, size_t size)
    {
        if constexpr (std::is_trivially_copyable_v<T>)
        {
            m_copier.memcpy(m_values, values, sizeof(T) * size);
        }
        else
        {
            std::copy_n(values, size, m_values);
        }
    }

    Copier& copier()
    {
        return m_copier;
    }

};

struct test_copier
{
    size_t m_memcpyUseCount{ 0 };

    void memcpy(void* dest, void const* src, size_t size)
    {
        std::memcpy(dest, src, size);
        ++m_memcpyUseCount;
    }
};

int main() {
    {
        my_vector<int, test_copier> vectorOfTriviallyCopyable;
        int buffer[2]{};
        vectorOfTriviallyCopyable.fill_first(buffer, std::size(buffer));
        assert(vectorOfTriviallyCopyable.copier().m_memcpyUseCount == 1);
    }

    {
        my_vector<std::string, test_copier> vectorOfNonTriviallyCopyable;
        std::string buffer[2]{};
        vectorOfNonTriviallyCopyable.fill_first(buffer, std::size(buffer));
        assert(vectorOfNonTriviallyCopyable.copier().m_memcpyUseCount == 0);
    }

    return 0;
}
fabian
  • 80,457
  • 12
  • 86
  • 114
0

Looks like there is some way to detect there is no copy-construction:

If a custom container is std-like ( which means that it can be parametrized by an allocator ) a mock allocator can be used to observe if any container's element copy-construction is done by observing allocator's construct method. And if there are no construct method calls you can be pretty sure that no copy construction was done.

Voivoid
  • 461
  • 3
  • 11