Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Profiler] Use a homemade implementation of linked-list #5284

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class CollectorBase

void Add(TRawSample&& sample) override
{
_collectedSamples.Add(std::forward<TRawSample>(sample));
_collectedSamples.Add(std::move(sample));
}

void TransformRawSample(const TRawSample& rawSample, std::shared_ptr<Sample>& sample)
Expand Down Expand Up @@ -150,9 +150,8 @@ class CollectorBase
{
public:
SamplesEnumeratorImpl(RawSamples<TRawSample> rawSamples, CollectorBase<TRawSample>* collector) :
_rawSamples{std::move(rawSamples)}, _collector{collector}
_rawSamples{std::move(rawSamples)}, _collector{collector}, _currentRawSample{_rawSamples.begin()}
{
_currentRawSample = _rawSamples.cbegin();
}

// Inherited via SamplesEnumerator
Expand All @@ -163,7 +162,7 @@ class CollectorBase

bool MoveNext(std::shared_ptr<Sample>& sample) override
{
if (_currentRawSample == _rawSamples.cend())
if (_currentRawSample == _rawSamples.end())
return false;

_collector->TransformRawSample(*_currentRawSample, sample);
Expand All @@ -175,7 +174,7 @@ class CollectorBase
private:
RawSamples<TRawSample> _rawSamples;
CollectorBase<TRawSample>* _collector;
typename RawSamples<TRawSample>::const_iterator _currentRawSample;
typename RawSamples<TRawSample>::iterator _currentRawSample;
};

private:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@
<ClInclude Include="ExporterBuilder.h" />
<ClInclude Include="FileHelper.h" />
<ClInclude Include="FrameworkThreadInfo.h" />
<ClInclude Include="LinkedList.hpp" />
<ClInclude Include="RawSamples.hpp" />
<ClInclude Include="SamplesEnumerator.h" />
<ClInclude Include="Success.h" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,9 @@
<ClInclude Include="FrameworkThreadInfo.h">
<Filter>Threads</Filter>
</ClInclude>
<ClInclude Include="SampleEnumerator.h">
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="RawSamples.hpp" />
<ClInclude Include="SamplesEnumerator.h" />
<ClInclude Include="LinkedList.hpp" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="OpSysTools.cpp">
Expand Down Expand Up @@ -653,4 +653,4 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>
</Project>
263 changes: 263 additions & 0 deletions profiler/src/ProfilerEngine/Datadog.Profiler.Native/LinkedList.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2022 Datadog, Inc.

#pragma once

#ifdef __has_include // Check if __has_include is present
#if __has_include(<memory_resource>) // Check for a standard library
#include <memory_resource>
namespace pmr {
using namespace std::pmr;
}
#elif __has_include(<experimental/memory_resource>) // Check for an experimental version
#include <experimental/memory_resource>
namespace pmr {
using namespace std::experimental::pmr;
}
#else // Not found at all
#error "Missing <memory_resource>"
#endif
#endif

#include <cassert>
#include <cstdlib>
#include <memory>
#include <stdexcept>
#include <utility>

#ifdef DD_TEST
#define NOEXCEPT noexcept(false)
#else
#define NOEXCEPT noexcept
#endif

template <class T>
class LinkedList
{
public:
LinkedList(pmr::memory_resource* allocator = pmr::get_default_resource()) noexcept :
_head{nullptr}, _tail{&_head}, _nbElements{0}, _allocator{allocator}
{
}

~LinkedList()
{
auto* current = std::exchange(_head, nullptr);

while (current != nullptr)
{
auto* next = current->Next;
// call Node dtor
std::destroy_at(current);
gleocadie marked this conversation as resolved.
Show resolved Hide resolved
_allocator->deallocate(current, sizeof(Node));
current = next;
}
}

LinkedList(LinkedList const&) = delete;
LinkedList& operator=(LinkedList const&) = delete;

LinkedList(LinkedList&& other) NOEXCEPT : LinkedList()
{
*this = std::move(other);
}

LinkedList& operator=(LinkedList&& other) NOEXCEPT
{
if (this == &other)
{
return *this;
}

Swap(other);

return *this;
}

void Swap(LinkedList& other)
{
if (this == &other)
{
return;
}

_head = std::exchange(other._head, _head);
_tail = std::exchange(other._tail, _tail);

if (_head == nullptr)
_tail = &_head;

if (other._head == nullptr)
other._tail = &other._head;

#ifdef DD_TEST
if (_head == nullptr && _tail != &_head)
{
throw std::runtime_error("_tail must have the address of _head");
}

if (other._head == nullptr && other._tail != &other._head)
{
throw std::runtime_error("other._tail must have the address of other._head");
}
#endif

std::swap(_nbElements, other._nbElements);
std::swap(_allocator, other._allocator);
}

bool Append(T&& v)
{
NodeGuard guard(_allocator);
guard.Allocate();

if (guard._Node == nullptr)
{
return false;
}

ConstructNode(guard._Node, std::move(v));

*_tail = guard.Release();
_tail = &((*_tail)->Next);

_nbElements++;

return true;
}

std::size_t Size() const
{
return _nbElements;
}

class iterator;

iterator begin()
{
return iterator(_head);
}

iterator end()
{
return iterator(nullptr);
}

private:
struct Node;

public:
class iterator
{
public:
iterator(Node* node) :
_Ptr{node}
{
}

T& operator*() const
{
return _Ptr->Value;
}

iterator operator++(int)
{
auto tmp = *this;
++*this;
return tmp;
}

iterator& operator++()
{
_Ptr = _Ptr->Next;
return *this;
}

bool operator==(iterator const& other) const
{
return _Ptr == other._Ptr;
}

bool operator!=(iterator const& other) const
{
return !(*this == other);
}

private:
Node* _Ptr;
};

private:
// Actual LinkedList node
struct Node
{
public:
T Value;
Node* Next;

template <class Ty>
Node(Ty&& v, Node* next) :
Value(std::forward<Ty>(v)), Next{next}
{
}

Node(Node const&) = delete;
Node& operator=(Node const&) = delete;
};

// Used to make sure the code is exception-safe when we Append an element.
// In case, construction throws, we won't leak memory.
struct NodeGuard
{
public:
constexpr explicit NodeGuard(pmr::memory_resource* mr) :
_mr{mr}, _Node{nullptr}
{
}

constexpr ~NodeGuard()
{
if (_Node != nullptr)
{
_mr->deallocate(_Node, sizeof(Node));
}
}

NodeGuard(NodeGuard const&) = delete;
NodeGuard& operator=(NodeGuard const&) = delete;

constexpr void Allocate()
{
_Node = reinterpret_cast<Node*>(_mr->allocate(sizeof(Node)));
}

[[nodiscard]] constexpr Node* Release()
{
return std::exchange(_Node, nullptr);
}

pmr::memory_resource* _mr;
Node* _Node;
};

constexpr void ConstructNode(void* ptr, T&& value)
{
new (ptr) Node(std::move(value), nullptr);
}

// On linux allocate has std::pmr::memory_resource::allocate function has the returns_nonnull attribute.
// This allows the compiler to optimize the code, by removing the null-check.
// We observed a segmentation fault in our CI with ASAN and UBSAN in the unit tests.
// We still want to keep the null-check, because when we will use (for some profilers) the
// ringbuffer-based memory_resource, it could return nullptr (when there is no more room)
inline void* allocateNode()
{
return _allocator->allocate(sizeof(Node));
}

Node* _head;
Node** _tail;
std::size_t _nbElements;

pmr::memory_resource* _allocator;
};
Original file line number Diff line number Diff line change
Expand Up @@ -414,4 +414,4 @@ inline void ManagedThreadInfo::SetSharedMemory(volatile int* memoryArea)
{
_sharedMemoryArea = memoryArea;
}
#endif
#endif
Loading
Loading