View Issue Details

IDProjectCategoryView StatusLast Update
0004225NoesisGUIC++ SDKpublic2025-06-05 12:05
Reporterjsantos Assigned Tojsantos  
PrioritynormalSeverityfeature 
Status assignedResolutionopen 
Product Version3.2.8 
Summary0004225: Optimize Delegate Implementation to Use Two-Pointer Thunks
Description

Our current delegate implementation is functional but not optimal in terms of performance and memory usage. We propose a significant optimization: a redesign of our delegates using only two pointers, one for this, and one function pointer to a template thunk corresponding to the delegate type (free function, member function, or lambda_ref).

Our delegates are fast but they could be faster and require less memory if we implemented then with just two pointers for all cases. The first pointer for this and the second one a function pointer to a template thunk for the corresponding case: func, member_func and lambdas.

Something like this:

template<class Ret, class ...Args>
class Function<Ret (Args...)>
{
public:
    using stub_type = Ret(*)(void*, Args...);

    Function() : _this(nullptr), _stub(nullptr) {}
    Function(void* this_, stub_type stub_) : _this(this_), _stub(stub_) {}

    template <Ret (*Func)(Args...)>
    Function& Assign()
    {
        _this = 0;
        _stub = FuncStub<Func>;
        return *this;
    }

    template <Ret (*Func)(Args...)>
    static Function<Ret(Args...)> Make()
    {
        return Function(nullptr, FuncStub<Func>);
    }

    template <class T, Ret(T::*Func)(Args...)>
    Function& Assign(T* this_, Ret(T::*Func)(Args...))
    {
        _this = this_;
        _stub = MemberStub<T, Func>;
        return *this;
    }

    template <class T, Ret(T::*Func)(Args...) >
    static Function<Ret(Args...)> Make(T* this_)
    {
        return Function(this_, MemberStub<T, Func>);
    }

    Ret operator()(Args... args) const
    {
        _stub(_this, args...);
    }

    template <Ret (*Func)(Args...)>
    static Ret FuncStub(void*, Args... args)
    {
        return (Func)(args...);
    }

    template <class T, Ret (T::*Func)(Args...)>
    static Ret MemberStub(void* this_, Args... args)
    {
        return ((T*)this_->*Func)(args...);
    }

    void* _this;
    stub_type _stub;
};

And the usage would be:

auto func0 = Function<void()>::Make<Prueba>();
func0.Assign<Prueba>();
func0.Assign<Entity, &Entity::Prueba>(&e);

auto func1 = Function<void()>::Make<Entity, &Entity::Prueba>(&e);

Benefits:

  • Performance: No virtual dispatch; stub function is selected at compile time. The resulting assembly is extremely efficient: just a single indirection.
  • Memory Efficiency: Only two pointers are stored, minimizing overhead.
  • Comparison: trivial comparison (two pointers) for all cases.
  • Simplicity: Vastly reduces the complexity of the implementation while still supporting the essential functionality.

Tradeoffs / Breaking Changes:

  • Breaking Change: Delegate creation now requires compile-time resolution. This breaks compatibility with the current runtime-binding approach.
  • Lambdas: Regular lambdas are no longer supported. Only lambda_ref (introduced in 3.2.8) will work. While this limits flexibility, it avoids the current situation where generic lambdas can be used but they can't be compared and the capture size is limited.

On top of this, we still need a vector for the multidelegate.

More information: https://www.codeproject.com/Articles/1170503/The-Impossibly-Fast-Cplusplus-Delegates-Fixed

PlatformAny

Activities

There are no notes attached to this issue.

Issue History

Date Modified Username Field Change
2025-06-05 11:46 jsantos New Issue
2025-06-05 11:46 jsantos Assigned To => jsantos
2025-06-05 11:46 jsantos Status new => assigned
2025-06-05 11:46 jsantos Description Updated
2025-06-05 11:49 jsantos Description Updated
2025-06-05 11:58 jsantos Description Updated
2025-06-05 11:59 jsantos Description Updated
2025-06-05 12:03 jsantos Description Updated
2025-06-05 12:05 jsantos Description Updated