1"use strict";(globalThis.webpackChunkwebsite=globalThis.webpackChunkwebsite||[]).push([[2745],{28453:(e,n,s)=>{s.d(n,{R:()=>o,x:()=>a});var t=s(96540);const r={},l=t.createContext(r);function o(e){const n=t.useContext(l);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),t.createElement(l.Provider,{value:n},e.children)}},61597:e=>{e.exports=JSON.parse('{"permalink":"/blog/2023/03/07/ruby-rust-dbus-speed","source":"@site/blog/2023-03-07-ruby-rust-dbus-speed.mdx","title":"Compare Calling D-Bus in Ruby and Rust","description":"Do some measurement of execution speed for rust and ruby when communicate with D-Bus","date":"2023-03-07T12:00:00.000Z","tags":[{"inline":false,"label":"Programming","permalink":"/blog/tags/programming","description":"Programming and development"},{"inline":false,"label":"Ruby","permalink":"/blog/tags/ruby","description":"Programming in Ruby language"},{"inline":false,"label":"Rust","permalink":"/blog/tags/rust","description":"Programming in Rust language"},{"inline":false,"label":"D-Bus","permalink":"/blog/tags/d-bus"}],"readingTime":4.25,"hasTruncateMarker":true,"authors":[],"frontMatter":{"date":"2023-03-07T12:00:00.000Z","title":"Compare Calling D-Bus in Ruby and Rust","description":"Do some measurement of execution speed for rust and ruby when communicate with D-Bus","tags":["programming","ruby","rust","d-bus"]},"unlisted":false,"prevItem":{"title":"Adding auto-installation support","permalink":"/blog/2023/03/14/auto-installation-support"},"nextItem":{"title":"Systemd Container and Podman in GitHub CI","permalink":"/blog/2023/03/03/github-ci"}}')},75806:(e,n,s)=>{s.r(n),s.d(n,{assets:()=>i,contentTitle:()=>a,default:()=>d,frontMatter:()=>o,metadata:()=>t,toc:()=>u});var t=s(61597),r=s(74848),l=s(28453);const o={date:new Date("2023-03-07T12:00:00.000Z"),title:"Compare Calling D-Bus in Ruby and Rust",description:"Do some measurement of execution speed for rust and ruby when communicate with D-Bus",tags:["programming","ruby","rust","d-bus"]},a=void 0,i={authorsImageUrls:[]},u=[{value:"Small CLI scenario",id:"single-call",level:2},{value:"Ruby code",id:"ruby-single-call",level:3},{value:"Rust code",id:"rust-single-call",level:3},{value:"Results",id:"results-single-call",level:3},{value:"Multiple calls scenario",id:"multiple-calls",level:2},{value:"Ruby code",id:"ruby-multiple-calls",level:3},{value:"Rust code",id:"rust-multiple-calls",level:3},{value:"Results",id:"results-multiple-calls",level:3}];function c(e){const n={a:"a",code:"code",h2:"h2",h3:"h3",p:"p",pre:"pre",...(0,l.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.p,{children:"For D-Installer we already have a Ruby CLI that was created as a proof of concept. Then as part of\nof the hackweek we created another one in Rust to learn a bit about Rust and get our hands dirty.\nNow that we are familiar with both, we want to measure the overhead of calling D-Bus methods in Rust\nand Ruby, to make sure that if we continue with Rust, we won't be surprised by its speed. speed\n(hint: we do not expect it, but expectations and facts may be different)."}),"\n","\n",(0,r.jsx)(n.h2,{id:"single-call",children:"Small CLI scenario"}),"\n",(0,r.jsx)(n.p,{children:"Since we want to measure mainly overhead, we use a simple program that reads a property from the\nd-bus and print it to stdout. The data structure of the property is not trivial, so the efficiency\nof so the efficiency of the data marshalling is also tested. We use the D-Bus interface we have in\nD-Installer and the property was a list of available base products."}),"\n",(0,r.jsxs)(n.p,{children:["The libraries used for communication with D-Bus are well known. For D-Bus we use\n",(0,r.jsx)(n.a,{href:"https://github.com/mvidner/ruby-dbus",children:"rubygem-dbus"})," and for rust we use\n",(0,r.jsx)(n.a,{href:"https://docs.rs/zbus/latest/zbus/",children:"zbus"}),". To keep the code simple, we do not use advanced stuff\nfrom the libraries like creating objects/proxies, but simple direct calls."]}),"\n",(0,r.jsx)(n.h3,{id:"ruby-single-call",children:"Ruby code"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rb",children:'require "dbus"\n\nsysbus = DBus.system_bus\nservice = sysbus["org.opensuse.DInstaller.Software"]\nobject = service["/org/opensuse/DInstaller/Software1"]\ninterface = object["org.opensuse.DInstaller.Software1"]\nproducts = interface["AvailableBaseProducts"]\nputs "output: #{products.inspect}"\n'})}),"\n",(0,r.jsx)(n.h3,{id:"rust-single-call",children:"Rust code"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",children:'use zbus::blocking::{Connection, Proxy};
1\n\nfn main() {\n let connection = Connection::system().unwrap();\n let proxy = Proxy::new(&connection,\n "org.opensuse.DInstaller.Software",\n "/org/opensuse/DInstaller/Software1",\n "org.opensuse.DInstaller.Software1").unwrap();\n let res: Vec<(String,String)> = proxy.get_property("AvailableBaseProducts").unwrap();\n println!("output: {:?}", res);\n return;\n}\n'})}),"\n",(0,r.jsx)(n.h3,{id:"results-single-call",children:"Results"}),"\n",(0,r.jsx)(n.p,{children:"To get some reasonable numbers, we run it a hundred times and measure it with the time utility."}),"\n",(0,r.jsx)(n.p,{children:"So here is the result for ruby 3.1:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"time for i in {1..100}; do ruby dbus_measure.rb &> /dev/null; done\n\nreal\t0m40.491s\nuser\t0m18.599s\nsys\t0m3.823s\n"})}),"\n",(0,r.jsx)(n.p,{children:"Here is the result for ruby 3.2:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"time for i in {1..100}; do ruby dbus_measure.rb &> /dev/null; done\n\nreal\t0m31.025s\nuser\t0m16.412s\nsys\t0m3.441s\n"})}),"\n",(0,r.jsxs)(n.p,{children:["And to compare rust one built with ",(0,r.jsx)(n.code,{children:"--release"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"time for i in {1..100}; do ./dbus_measure &> /dev/null; done\n\nreal\t0m10.286s\nuser\t0m0.254s\nsys\t0m0.188s\n"})}),"\n",(0,r.jsxs)(n.p,{children:["As you can see, the rust looks much faster. It is also nice to see that in Ruby3.2 the cold start\nhas been has been nicely improved. We also discussed this with the ruby-dbus maintainer and he\nmentioned that ruby dbus calls introspection on object and there is a\n",(0,r.jsx)(n.a,{href:"https://github.com/mvidner/ruby-dbus/blob/master/examples/no-introspect/tracker-test.rb",children:"way to avoid this"})]}),"\n",(0,r.jsx)(n.p,{children:"Overall impression is that if you want a small CLI utility that needs to call d-bus, then rust is\nmuch better for it."}),"\n",(0,r.jsx)(n.h2,{id:"multiple-calls",children:"Multiple calls scenario"}),"\n",(0,r.jsx)(n.p,{children:"Our CLI in some cases is really just a single dbus call like when you set some DInstaller option,\nbut there are other cases like a long running probe that needs progress reporting, and in that case\nthere will be many more dbus calls. So we want to simulate the case where we need to call progress\nmultiple times during a single run."}),"\n",(0,r.jsx)(n.h3,{id:"ruby-multiple-calls",children:"Ruby code"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rb",children:'require "dbus"\n\nsysbus = DBus.system_bus\nservice = sysbus["org.opensuse.DInstaller.Software"]\nobject = service["/org/opensuse/DInstaller/Software1"]\ninterface = object["org.opensuse.DInstaller.Software1"]\n\n100.times do\n products = interface["AvailableBaseProducts"]\nend\n'})}),"\n",(0,r.jsx)(n.h3,{id:"rust-multiple-calls",children:"Rust code"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",children:'use zbus::blocking::{Connection, Proxy};\n\nfn main() {\n let connection = Connection::system().unwrap();\n let proxy = Proxy::new(&connection,\n "org.opensuse.DInstaller.Software",\n "/org/opensuse/DInstaller/Software1",\n "org.opensuse.DInstaller.Software1").unwrap();\n for _ in 1..100 {\n let _: Vec<(String,String)> = proxy.get_property("AvailableBaseProducts").unwrap();\n }\n return;\n}\n'})}),"\n",(0,r.jsx)(n.h3,{id:"results-multiple-calls",children:"Results"}),"\n",(0,r.jsx)(n.p,{children:"We see no difference for different Ruby versions, so we just show the times:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"time ruby dbus_measure.rb\n\nreal\t0m10.529s\nuser\t0m0.372s\nsys\t0m0.039s\n\n\ntime ./dbus_measure\n\nreal\t0m0.052s\nuser\t0m0.005s\nsys\t0m0.003s\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Here it gets even more interesting and reason reveals\n",(0,r.jsx)(n.code,{children:"busctl --system monitor org.opensuse.DInstaller.Software"}),". Rust caches the property in its proxy\nand just does a single dbus call ",(0,r.jsx)(n.code,{children:"GetAll"})," to init all its properties. On the other hand, the ruby\nlibrary calls introspection first and then calls ",(0,r.jsx)(n.code,{children:"Get"})," with the property specified. Same behaviour\ncan be achieved in ruby too, but it is more work. So even for simple CLI that needs multiple calls\nto D-Bus, rust looks fast enough. Only remaining question we need to answer is whether rust proxy\ncorrectly detects when a property is changed (in other words, when it sends observer signals by\ndefault)."]}
1),"\n",(0,r.jsxs)(n.p,{children:["For this reason we create a simple rust program with sleep and use ",(0,r.jsx)(n.code,{children:"d-feet"})," to change property."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",children:'use zbus::blocking::{Connection, Proxy};\nuse std::{thread, time};\n\nfn main() {\n let connection = Connection::system().unwrap();\n let proxy = Proxy::new(&connection,\n "org.opensuse.DInstaller.Software",\n "/org/opensuse/DInstaller/Software1",\n "org.opensuse.DInstaller.Software1").unwrap();\n let second = time::Duration::from_secs(1);\n for _ in 1..100 {\n let res: String = proxy.get_property("SelectedBaseProduct").unwrap();\n println!("output {}", res);\n thread::sleep(second)\n }\n return;\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"And we have successfully verified that everything is working as it should in the rust."})]})}function d(e={}){const{wrapper:n}={...(0,l.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(c,{...e})}):c(e)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.