Skip to content

Improve: binaryops with number protocol #4139

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

Closed
wants to merge 2 commits into from

Conversation

Karatuss
Copy link
Contributor

@Karatuss Karatuss commented Aug 28, 2022

Environments

  • Use rustpython binary generated after using cargo run --release for faster runtime than debug one.

Tasks

  • 1. Apply only for multiply for test
  • 2. Apply for every number methods
    • Sub-tasks
      • PyNumberBinaryOp implementation
      • extra_tests/snippets/builtin_complex.py: fix multiply
      • or => type_::or_() problem
      • and => set type doesn't have number protocol
      • mod(remainder) => str module - implant number protocol, compatible with bytes.rs's remainder method
  • 3. Refactor relative codes being useless, and so on.

Profiling

Benchmark source

Let me know if you have any good idea for improved benchmark!

# benches/microbenchmarks/multiply.py

import pyperf
import sys

ITERATIONS = 1000000

def multiply():
    integer = 1
    floating = 1.0
    byte_str = b'1'

    for i in range(ITERATIONS):
        integer = (integer * i) % sys.maxsize
        floating = (floating * float(i)) % sys.maxsize
        byte_str = byte_str * (i % 1000)
        # a = i % sys.maxsize
        # complexing = complex(a, a) * a

# multiply()
if __name__ == "__main__":
    runner = pyperf.Runner()
    runner.bench_func('multiply', multiply)

flamegraph

Before
flame_before

After
flame_after

You can check the call-stack has decreased comparing with before!

pyperf

This represents binaryops with number protocol can improve RustPython's performance just by applying only for multiplying operation.

+-----------+-----------------------+---------------------------+----------------------------+
| Benchmark | multiply_cpython_3_10 | multiply_rustpython_after | multiply_rustpython_before |
+===========+=======================+===========================+============================+
| multiply  | 340 ms                | 5.57 sec: 16.36x slower   | 6.14 sec: 18.05x slower    |
+-----------+-----------------------+---------------------------+----------------------------+

@Karatuss Karatuss force-pushed the improve/vm_ops-pynumber branch from 6b4373a to b8619f1 Compare August 28, 2022 08:47
@youknowone youknowone requested a review from qingshi163 August 28, 2022 16:36
@youknowone

This comment was marked as outdated.

@Karatuss

This comment was marked as outdated.

@youknowone youknowone added the z-ca-2022 Tag to track contrubution-academy 2022 label Aug 29, 2022
@Karatuss Karatuss force-pushed the improve/vm_ops-pynumber branch from 844a5b6 to ee890ad Compare August 29, 2022 08:47
@@ -237,8 +330,12 @@ impl PyNumber<'_> {
obj.class().mro_find_map(|x| x.slots.as_number.load())
}

pub fn methods(&self) -> &PyNumberMethods {
self.methods
pub fn methods<'a>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn methods<'a>(
pub fn binary_op<'a>(

let seq_a = PySequence::new(a, vm);
let seq_b = PySequence::new(b, vm);

// TODO: I think converting to isize process should be handled in repeat function.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

who is "I"? the future reader will be confused.

If you think this topic requires discussion rather than leaving concrete TODO lists,
please consider opening an issue and leave here the issue number or link.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If allowing arbitary n is not necessary for cpython compatibility, I think repeat is better to take isize or usize because we can skip range check when we already know repeating n is possible.
If we are going to remove repetitive pattern, adding try_repeat with more inclusive n will be possible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another idea is this will be a temporary code.
once all sequences implement number protocol correctly, they will handle it in their own number protocol.


if let Some(slot_a) = slot_a {
if let Some(slot_b) = slot_b {
// Check if `a` is subclass of `b`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@youknowone

According to CPython implementation https://github.com/python/cpython/blob/main/Objects/abstract.c#L842 , we may need to ensure b is a "strict subclass" of a in order to use slot_b for operation.

fast_isinstance method seems including the situation that a & b belong to the same class, https://github.com/RustPython/RustPython/blob/main/vm/src/object/ext.rs#L459 .
Is there a method or proper approach to check "strict subclass" please?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we have a function to do that.
But how about !a.is(b) && a.fast_issubclass(b)?
when a and b are identical type, a.is(b) will be true

&'a self,
op_slot: &'a PyNumberMethodsOffset,
vm: &VirtualMachine,
) -> PyResult<&BinaryFunc> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we keep the name binary_op, the return type will be something other than BinaryFunc

// Check if `a` is subclass of `b`
if b.fast_isinstance(a.class()) {
let ret = slot_b(num_a, b, self)?;
if ret.rich_compare_bool(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@youknowone
Is rich_compare_bool slow since it calls _cmp which is not a trivial function?
Is it the suggested way to check whether ret is self.ctx.not_implemented?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using ret.fast_isinstance(PyNotImplemented::class(self)) to check self.ctx.not_implemented?

Copy link
Member

@youknowone youknowone Mar 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because NotImplemented is a singleton, I think this check can be fine:

ret.is(&self.ctx.not_implemented)

But not 100% sure because some type can override operators in weird way and rich_compare_bool will respect it. I have no idea about how CPython handle it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed the second comment. In this case, fast_isinstance doesn't have any advantage to simple is calling. Because fast_isinstance doesn't check __isinstance__ python-implemented weird things will not be covered. But because NotImplemented is always guaranteed to be singleton, fast_isinstance doesn't check more thing. So fast_isinstance is not strong as rich_compare but same weak as simple is.

youknowone pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 3, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 3, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 4, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 5, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 5, 2023
youknowone pushed a commit to youknowone/RustPython that referenced this pull request Mar 5, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 6, 2023
youknowone pushed a commit to youknowone/RustPython that referenced this pull request Mar 6, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 6, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 6, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 7, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 7, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 7, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 7, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 7, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 7, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 9, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 9, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 9, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 9, 2023
xiaozhiyan pushed a commit to xiaozhiyan/RustPython that referenced this pull request Mar 9, 2023
minhrongcon2000 pushed a commit to minhrongcon2000/RustPython that referenced this pull request Mar 10, 2023
@youknowone
Copy link
Member

merged through #4615

@youknowone youknowone closed this Mar 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
z-ca-2022 Tag to track contrubution-academy 2022
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants