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

Can I make safe template dynamic methods? #14

Closed
uselessgoddess opened this issue Jul 12, 2022 · 25 comments
Closed

Can I make safe template dynamic methods? #14

uselessgoddess opened this issue Jul 12, 2022 · 25 comments

Comments

@uselessgoddess
Copy link

That's what I'm talking about, but in Rust

trait Trait {
    fn foo(&self, bytes: &[u8]);

    fn bar(&self, bytes: impl AsRef<[u8]>)
    where
        Self: Sized,
    {
        self.foo(bytes.as_ref());
    }
}

impl<T: Trait + ?Sized> Trait for Box<T> {
    fn foo(&self, bytes: &[u8]) {
        (**self).foo(bytes);
    }
}

struct A;
impl Trait for A {
    fn foo(&self, bytes: &[u8]) {
        println!("{:?}", bytes);
    }
}

fn main() {
    let a: Box<dyn Trait> = box A;

    a.bar("hello");
    a.bar(b"hello");
    a.bar([104, 101, 108, 108, 111]);
    a.bar(String::from("hello"));
}
@uselessgoddess uselessgoddess changed the title Can I make safe template dynamic ~~methods~~ functions? Can I make safe template dynamic methods? Jul 12, 2022
@kelbon
Copy link
Owner

kelbon commented Jul 13, 2022

That's what I'm talking about, but in Rust

As i understand impl AsRef here is a some type which has member function .as_ref
There are two ways to do it, first: compiler adds a function into vtable for each type with which you use the method bar from trait/ or for each impl of AsRef

And second way - type erase second argument too.
So in this case you can do bar(const T& self, std::string_view OR std::span<std::byte> bytes)

@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 13, 2022

compiler adds a function into vtable for each type with which you use the method bar from trait/ or for each impl of AsRef

No, no. I need bar to be a method outside vtable. It should take template arguments and send them in converted form to methods from vtable.

I want this:

template<typename Self>
struct Foo {
    static void do_invoke(const Self& self, std::span<const std::byte> bytes) {
        self.foo(bytes);
    }
};

using any_foo = aa::any_with<Foo>;

auto bar(any_foo self, const auto& bytes) {
    return aa::invoke<Foo>(self, std::as_bytes(std::span(bytes)));
}

struct A {
    void foo(std::span<const std::byte> bytes) const {
        for (auto&& b : bytes) {
            std::cout << static_cast<std::size_t>(b) << " ";
        }
        std::cout << std::endl;
    }
};

auto main() -> int {
    auto a = A{};
    
    bar(a, std::string("hello"));
    bar(a, "hello");
    bar(a, u8"hello");
    bar(a, std::to_array<std::uint8_t>({104, 101, 108, 108, 111}));
}

@kelbon
Copy link
Owner

kelbon commented Jul 13, 2022

I need bar to be a method outside vtable. It should take template arguments and send them in converted form to methods from vtable.

Okay, then you can use plugins:

template<typename Self>
struct Foo {
    static void do_invoke(const Self& self, std::span<const std::byte> bytes) {
        self.foo(bytes);
    }

    template<typename CRTP>
    struct plugin {
        auto bar(const auto& bytes) const {
            return aa::invoke<Foo>(*static_cast<const CRTP*>(this), std::as_bytes(std::span(bytes)));
        }
    };
};

struct A {
    void foo(std::span<const std::byte> bytes) const {
        for (auto&& b : bytes) {
            std::cout << static_cast<std::size_t>(b) << " ";
        }
        std::cout << std::endl;
    }
};

auto main() -> int {
    auto value = A{};
     // more effective then create polymorphic value like any_foo
    aa::const_poly_ref<Foo> a = value;

    a.bar("hello");
    a.bar(u8"hello");
    a.bar(std::to_array<std::uint8_t>({104, 101, 108, 108, 111}));
}

https://godbolt.org/z/1TeM7TPTj
(more effective then rust...?)

@uselessgoddess
Copy link
Author

Ok, thanks, that works)

@kelbon
Copy link
Owner

kelbon commented Jul 13, 2022

Ok, thanks, that works)

Can you please tell me is that code in rust does same thing as C++?
https://godbolt.org/z/z955q9dex
Why std::process::exit takes i32 while main can returns only smth with Termination, in C++ std::exit take int and main returns int.
Why byte in loop is a 'ref'(pointer)?

@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 13, 2022

Why std::process::exit takes i32 while main can returns only smth with Termination, in C++ std::exit take int and main returns int.

I don't know. This is a new hype feature.

Why byte in loop is a 'ref'(pointer)?

This is because of borrow semantic, it is not so easy to explain
You can write

for byte /* : &u8 */ in bytes.iter() {
    result = result + *byte as usize;
}
// ----
for byte /* : u8 */ in bytes.iter().cloned() {
    result = result + byte as usize;
}

P.S. code in rust does same thing as C++ (almost)

@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 13, 2022

I don't know. This is a new hype feature.

In rust exit code can be only u8
https://doc.rust-lang.org/src/std/process.rs.html#1794-1799

@kelbon
Copy link
Owner

kelbon commented Jul 13, 2022

but i can return i32 in std::process:exit.
I may be need this example for my paper...

@uselessgoddess
Copy link
Author

@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 13, 2022

more effective then rust...?

I make very rough benchmarks (100 million calls per sec):
&dyn Foo/Box<dyn Foo> – 90-110ms
poly_ref<Foo> – 120-140ms
any_with<Foo> – 400ms+(I'm crying)

I can rewrite bench:

// from
aa::any_with<Foo> a = value;
// to
aa::any_with<Foo> b = value;
auto /* poly_ref */ a = *&b;

Why any_with so slow?

@kelbon
Copy link
Owner

kelbon commented Jul 13, 2022

Why any_with so slow?

Can i see all code? May be you allocate every time because your type has throw move constructor or smth
P.S. if you invoke smth with cout, then its cout problem, synchronization etc

@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 13, 2022

Sure, without IO. I use tiny example. Also, if you use the functions (insted of macro bench), then poly_ref working at any_with speed
https://godbolt.org/z/oMrK4h8dG

@kelbon
Copy link
Owner

kelbon commented Jul 13, 2022

Sure, without IO. I use tiny example. Also, if you use the functions (insted of macro bench), then poly_ref working at any_with speed https://godbolt.org/z/oMrK4h8dG

I think it's just an accident of clang limits for inlining and the benchmark is designed so that the lack of an inline call guarantees insanely slow results.
Also note that the compiler usually cannot inline such a call, because the type (and function pointer) is unknown at compile time. So this is not a typical application of this type.
If we consider a simple call through a table, then there is literally one-on-one code with virtual functions and it seems faster not to do
https://godbolt.org/z/4ca6rYG89

@uselessgoddess
Copy link
Author

This is strange, template function does not erase type info

@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 13, 2022

I just rebench and think any_which has problems with optimizing the call of the same similar functions
https://godbolt.org/z/4YW4je33v
Please, run at local machine. I'm really confused

@kelbon
Copy link
Owner

kelbon commented Jul 13, 2022

I just rebench and think any_which has problems with optimizing the call of the same similar functions https://godbolt.org/z/4YW4je33v Please, run at local machine. I'm really confused

i will try, maybe compiler thinks invoke can change state of any_with and dont assumes that pointer will be same. Need to see assembly...

@kelbon
Copy link
Owner

kelbon commented Jul 13, 2022

I just rebench and think any_which has problems with optimizing the call of the same similar functions https://godbolt.org/z/4YW4je33v Please, run at local machine. I'm really confused

why you use virtual functions in second case? Its double erase or smth.

@uselessgoddess
Copy link
Author

why you use virtual functions in second case? Its double erase or smth.

This is to test compiler optimization in calls of raw virtual functions. If you talk about Bar

@kelbon
Copy link
Owner

kelbon commented Jul 13, 2022

why you use virtual functions in second case? Its double erase or smth.

This is to test compiler optimization in calls of raw virtual functions. If you talk about Bar

Its stupid, but are you sure godbolt uses same archs/ machines to run rust and C++ code? )) xD
And im pretty sure smth wrong with rust compiler on godbolt:
image

@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 13, 2022

Its stupid, but are you sure godbolt uses same archs/ machines to run rust and C++ code? )) xD

No, but godbolt uses same xeons (I hope)

And im pretty sure smth wrong with rust compiler on godbolt:

Uh, this is known bug. Please add pub keyword to main function

@kelbon
Copy link
Owner

kelbon commented Jul 13, 2022

Its stupid, but are you sure godbolt uses same archs/ machines to run rust and C++ code? )) xD

No, but godbolt uses same xeons (I hope)

And im pretty sure smth wrong with rust compiler on godbolt:

Uh, this is known bug. Please add pub keyword to main function

Okay, my last words about it... May be black box (in language thing for benchmarking) is a bad design idea, which obviously forces compiler(the only one rust compiler) to do smth with limits or i dont know to win benchmarks ?)) Lol.

https://godbolt.org/z/oc1YKahc8

Looks like the results are different without it

@uselessgoddess
Copy link
Author

Oh, my Jesus. What is it let mut result : i64 = 0; ((
Just believe in rust type inference, write let mut result = 0.

About black_box. Hm, I think is not bad. It's almost benchmark::DoNotOptimize from gtest
Possible implementation:

fn black_box<T>(dummy: T) -> T {
    unsafe {
        let ret = std::ptr::read_volatile(&dummy);
        std::mem::forget(dummy);
        ret
    }
}

https://stackoverflow.com/questions/14950614/working-of-asm-volatile-memory

@uselessgoddess
Copy link
Author

or i dont know to win benchmarks ?)) Lol.

Clang can't win llvm))

@uselessgoddess
Copy link
Author

uselessgoddess commented Jul 13, 2022

I think gcc can win, but... Clang has unreal optimization in this case
https://godbolt.org/z/h4hE8Pr4v

@uselessgoddess
Copy link
Author

black box (in language thing for benchmarking) is a bad design idea

I don't make cross-language benches anymore((
https://godbolt.org/z/EqETeK9nT

@kelbon kelbon closed this as completed Jul 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants