apache / fury

A blazingly fast multi-language serialization framework powered by JIT and zero-copy.
https://fury.apache.org/
Apache License 2.0
3.12k stars 248 forks source link

[Question] Could use Python bindings instead of Cython #1887

Open penguin-wwy opened 1 month ago

penguin-wwy commented 1 month ago

From the code, the Buffer in Cython is just a wrapper around the Buffer in cpp. It might be beneficial to use Python bindings directly, as this could reduce performance overhead and the cost of code maintenance.

chaokunyang commented 1 month ago

You mean implement Buffer using python only instead of CBuffer? We implement some varint encoding in Cpp Buffer. I'm not sure using python will make it faster, maybe need some benchmarks

penguin-wwy commented 1 month ago

My suggestion is to use pybind11 or directly write a C-API to call the cpp fury::Buffer. The current Cython-generated C++ code still calls the fury::Buffer, which I think is unnecessary.

static CYTHON_INLINE int64_t __pyx_f_6pyfury_5_util_6Buffer_get_int64(struct __pyx_obj_6pyfury_5_util_Buffer *__pyx_v_self, uint32_t __pyx_v_offset, CYTHON_UNUSED int __pyx_skip_dispatch) {
  ...

  /* "pyfury/_util.pyx":140
 *     cpdef inline int64_t get_int64(self, uint32_t offset):
 *         self.check_bound(offset, <int32_t>8)
 *         return self.c_buffer.get().GetInt64(offset)             # <<<<<<<<<<<<<<
 * 
 *     cpdef inline float get_float(self, uint32_t offset):
 */
  __pyx_r = __pyx_v_self->c_buffer.get()->GetInt64(__pyx_v_offset);  // This is an indirect call, which is not really necessary.
  goto __pyx_L0;

  /* function exit code */
  ...
}

If you agree, I will try to provide a simple demo to verify the performance and code maintenance benefits.

chaokunyang commented 1 month ago

That would be great, if we get a big performance gain, we can compromise some code maintenance costs

penguin-wwy commented 1 month ago

Hi, I conducted comparative experiments, trying out pybind11, nanobind, and directly writing C-API code.

static PyMethodDef cbuffer_methods[] = { {"get_bool", (PyCFunction)cbuffer_get_bool, METH_O, nullptr}, ... {NULL, NULL} / sentinel / };



Additionally, after analyzing the Cython code, I found that some performance optimizations can be achieved by directly calling certain C-API functions in the .pyx file. The principle behind this is to use some higher-level knowledge to avoid Cython generating certain guard code. I will attempt to submit these optimizations as a PR in the future.
chaokunyang commented 1 month ago

Thanks for sharing so insightful experiments. I used to think pybind is fast since it avoid generating code like cython. It turns out it's the slowest, this superised me a little. Cython seems provide some annotations to help generate c++ code, are you going to employ such kind of optimization?

Superskyyy commented 4 days ago

I suggest using Nanobind, nanobind is the next generation of pybind11 from the same guy that invented it. It is suggested officially to use nanobind over Pybind11. Python 3.8+ is not a problem since anything below is already subset by CPython.