-
-
Notifications
You must be signed in to change notification settings - Fork 10
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
Optimize performance of area draw methods #75
Comments
I admit that there is a problem with the slow drawing of LibUI. It is suggested that a switch from Fiddle to C extension may be necessary to solve this problem. I think this problem is too big to address all at once. I will try to think of a way to break the problem up and address it little by little. |
The reason I hesitated to take on this issue was because I thought I would need to rewrite many structs from Fiddle::Struct to TypedData Objects. This means redoing the entire LibUI. While I've written several C extension Gems before, most of In fact, we don't need to make major changes to deal with this issue. Just get the address from a Fiddle::Pointer object and pass it to the libui function. I've learned from kou how to implement a method in a C extension that takes FFI::Pointer as an argument. You can also write C extensions that take Fiddle::Pointer as arguments in a similar manner. |
I created a branch named native and added some draw-related methods written in C, mostly by AI coding assistants. This attempt is just getting started, but I have a feeling it won't be fast. We need to investigate what is the bottleneck. |
I got accepted to present at RubyConf again in 2024. I will conduct a 2-hour workshop just like last year. I will teach "How To Build Basic Desktop Applications in Ruby". It would be nice if the libui performance optimization for area draw methods was ready by November 13 when RubyConf 2024 begins. |
Unfortunately, I don’t think we’ll find a solution soon. Since it’s been a while, I wanted to share my thoughts. I don’t think the performance issues are because of libffi. This is just a guess, but when I tested ruby-htslib’s performance, libffi didn’t seem to cause any big slowdowns. I’m starting to think the real problem is with Ruby’s callbacks, which are closures. In C, callbacks are just functions, but Fiddle and Ruby-FFI create callback functions as closures. This is a big difference, and it might be causing the performance problems. Maybe creating and copying these closures is what’s slowing things down. Honestly, I feel a bit stuck this time, but I’ll keep investigating. |
Some Basic Understandings
Current Hypotheses
|
It might still be a useful idea to try the DragonRuby Game Toolkit automatic generator of Ruby C Extensions based on C Headers like the libui-ng header. It wouldn't hurt to try it and see what performance it offers even if you might decide not to rely on it long-term or not to rely on it for all libui methods (like by keeping some bound with ffi). I am sure trying the DragonRuby Game Toolkit automatic C extension generator is a good learning exercise regardless. |
I do not have a complete understanding of this matter. I have written what I think at this point in time in Japanese and had ChatGPT summarize it in English. Of course, what I have written here is not everything, but this is what I believe to be the most important aspect of this issue at this time. I must apologize for not being able to dedicate enough time and effort to this problem. However, I believe that simply replacing FFI with a C extension will not solve the issue. Issues with Using Closures as C CallbacksIn general, C functions cannot take closures (functions that keep track of external variables) as arguments. Instead, C functions often use a In Ruby, closures are represented by blocks, and a block can reference variables outside of itself. On the other hand, Ruby’s methods cannot reference external variables. This is a key difference between C functions and Ruby’s blocks or methods. Using Closures in CrystalTo understand this better, let's look at Crystal, a language similar to Ruby. In Crystal, you can use the Crystal Official Documentation: Passing a Closure to a C Function module Ticker
@@box : Pointer(Void)?
def self.on_tick(&callback : Int32 ->)
boxed_data = Box.box(callback)
@@box = boxed_data
LibTicker.on_tick(->(tick, data) {
data_as_callback = Box(typeof(callback)).unbox(data)
data_as_callback.call(tick)
}, boxed_data)
end
end In this example, the Limitations When There Is No
|
Thank you for the summary. Using methods in place of block closures should be good enough. If you can provide an example of that working without performance issues, that would be great.
This can only be confirmed 100% by trying to build a C extension even if in theory, this might be true. If the other option of using methods doesn’t work out, I think trying to build a C extension would be worth it even if it ends up failing in resolving the draw slowdown issue. |
I'm going to work on this problem this week, but please don't get your hopes up too much. |
This week, I made some progress in my thinking. It all started when I began using per to measure performance and Hotspot to visualize it. Although I don’t have a formal background in computing, I knew that Hotspot makes it easy for anyone to visualize how much time native functions consume. AndyObtiva/glimmer-dsl-libui#55 From the screenshots, I noticed that some functions, marked with At first, I thought the missing function names were because libui-ng wasn’t built with debug mode enabled. But even after rebuilding it with debug mode, nothing changed. Although libui-ng functions were recorded, the unknown function names remained hidden. Next, I suspected that these were dynamically generated anonymous functions from libffi. Since performance slowed down significantly with larger inputs (e.g., 1000 or 2000 elements) and much time was spent inside these unknown functions, I thought memory copying might be involved. I also wondered if FFI itself was responsible for copying memory, but this turned out to be a misunderstanding. The following blog explains how FFI calls back functions: According to the blog, if Furthermore, both FFI::Closure and FFI::Closure::BlockCaller use the same trampoline mechanism, meaning there is little practical difference between them. Thus, switching from FFI::Closure::BlockCaller to FFI::Closure will not solve the issue. However, memory copying does happen, but it occurs inside Ruby, not FFI. That’s what I have figured out so far... |
Hi,
I am opening an issue here that is related to the following issues in other projects:
The work that needs to be done was mentioned in these comments on the libui-ng issue:
As you already saw on Twitter, I announced that a 2-hour workshop that I proposed for RubyConf 2023 was accepted for November 13, 2023 (Workshop Title: How To Build Desktop Applications in Ruby): https://rubyconf-2023.sessionize.com/session/531448
Just like how I announced a new release of glimmer-dsl-libui in RubyConf 2022 that you helped contribute to in a new release of LibUI, it would be great if I could announce in RubyConf 2023 a new release of glimmer-dsl-libui that includes optimized area drawing via an optimized LibUI Ruby binding with a C extension for the uiDraw calls only (
LibUI.draw_*
methods likedraw_path_arc_to
,draw_path_line_to
, anddraw_path_add_rectangle
).If that is not possible by November 13, 2023, then if you could make a new release by then to resolve other simpler issues or expose new behavior from libui-ng, that would be great too (unless you have already exposed all the latest features in libui-ng).
Otherwise, it would still be useful to have this optimization implemented eventually as it would be great for a variety of graphical applications like games, live diagramming applications with thousands of nodes, and analytical applications.
The text was updated successfully, but these errors were encountered: