Nice!
Will the call performance continue approaching the QT signal-slot architecture or do you think you are already there?
It's much closer. Google says QT signal/slots are about 10x slower than a C++ virtual method call. Virtual calls are about 2-3x slower than a direct call. Meaning slots are 20-30x slower than a direct call.
That means a Sigils call is probably about 2-3x slower than calling a QT slot. Without actually testing it I can't be sure.
For local calls Sigils should be able to be sped up to match QT. Currently all parameters are packed into a ref object which incurs extra overhead and is not needed for local calls.
Support for local queued calls has been added with connectQueued! It's part of the threading module, but doesn't require a separate. It builds on the event loop and queue used for threading support though.
It's similar to "run soon" in async. Made it really easy to solve a work problem where I wanted a thread to do some work downloading data in batches or when triggered by new work.
I also had GPT-5 write docs for how the threading support works but the result is t the best. Still probably better than nothing.
New Sigils 0.17.1 release. It speeds up calls another 40% or so! That's about 50x slower than a native proc call.
Sigils has been running pretty well in my (limited) production IoT MVPs combined with Mummy. I've tied websocket updates to a signal for updated data. It's working like a charm and all multi-threaded using sqlite3!
There's also support for using CBOR binary serialization which will enabled backends like nimscript and javascript, but I haven't tried it on those yet.
"only 50x slower"? That's massive, why is it so slow?
It is, though in context QT's signals and slots is about 10x slower than a C++ virtual method call. So now Sigils is within 5x of QT's implementation. So it's still "massive" but starting to approach QT's performance.
Similarly Objective-C's objc_msgSend overhead can be "10x-100x on cache miss" compared to a C function. So now it's in the ballpark for the core message passing primitive of iOS/macOS on it's slow path.
The overhead sounds huge, but it's very useable for the target domains already.
The dynamic abilities are worth the extra overhead in the target domains. Not for an inner loop, but being able to do data buses and fans out, etc, would add significant overhead as well.
Sigils intentionally uses a serialization abstraction on arguments to support things like send data in a channel, or queue up a call for later. For non-threaded calls it just uses a variant library, but it still adds overhead.
Profiling it now shows more of a mix of many things remaining:
I worked on something similar over a decade ago in C++: https://github.com/miguelmartin75/wink-signals when I was deep into learning C++ template meta-programming during high school / first year of uni - the library is basically a glorified wrapper for https://www.codeproject.com/articles/Member-Function-Pointers-and-the-Fastest-Possible
Looking back at the code, I'm not sure I trust the benchmark I wrote lol
Looking back at the code, I'm not sure I trust the benchmark I wrote lol
Writing good benchmarks is hard. It's pretty easy to "lie" to yourself too, lol.
I worked on something similar over a decade ago in C++: https://github.com/miguelmartin75/wink-signals
Nice! Fun reading it.
Sigils v0.18.1 is out.
This release includes a new feature I've been wanting for a while: signals between multiple (real) threads!
The multi-threading support won't win benchmarks, however, it's meant for easily building scalable multi-threaded event based systems and should still be pretty performant. Think things such as ETL pipelines would be an ideal fit. It's sorta hybrid between Go and Erlang. In theory it event supports backpressure but that's not tested yet.
There's also timers based on std/selectors based on a pattern I saw in Mummy's examples which lets you combine timers and socket events without needing full on async system like asyncdispatch or chronos.
BTW, this multi-threading pattern works very well with Mummy! I built it to help me with a robot control web server / web page combo I'm building for work. Without this I needed a bunch of locks on objects or to make lots of channels, etc. Super annoying. Sigils should make it super easy now.
Here's a full working example with multiple threads that passes tsan with mm:arc:
import sigils, sigils/threads
type
Trigger = ref object of Agent
Worker = ref object of Agent
value: int
Collector = ref object of Agent
a: int
b: int
proc valueChanged(tp: Trigger, val: int) {.signal.}
proc updated(tp: Worker, final: int) {.signal.}
proc setValue(self: Worker, value: int) {.slot.} =
self.value = value
echo "worker:setValue: ", value, " (th: ", getThreadId(), ")"
emit self.updated(self.value)
proc gotA(self: Collector, final: int) {.slot.} =
echo "collector: gotA: ", final, " (th: ", getThreadId(), ")"
self.a = final
proc gotB(self: Collector, final: int) {.slot.} =
echo "collector: gotB: ", final, " (th: ", getThreadId(), ")"
self.b = final
let trigger = Trigger()
let collector = Collector()
let threadA = newSigilThread()
let threadB = newSigilThread()
threadA.start()
threadB.start()
startLocalThreadDefault()
var wA = Worker() # needed so we can move the ref object and have refcount == 1
var wB = Worker()
let workerA: AgentProxy[Worker] = wA.moveToThread(threadA)
let workerB: AgentProxy[Worker] = wB.moveToThread(threadB)
connectThreaded(trigger, valueChanged, workerA, setValue)
connectThreaded(trigger, valueChanged, workerB, setValue)
connectThreaded(workerA, updated, collector, Collector.gotA())
connectThreaded(workerB, updated, collector, Collector.gotB())
emit trigger.valueChanged(42)
let ct = getCurrentSigilThread()
discard ct.poll() # workerA result
discard ct.poll() # workerB result # or call ct.runForever()
doAssert collector.a == 42
doAssert collector.b == 42
setRunning(threadA, false)
setRunning(threadB, false)
threadA.join()
threadB.join()
Outputs:
worker:setValue: 42 (th: 630963)
worker:setValue: 42 (th: 630964)
collector: gotB: 42 (th: 405782)
collector: gotA: 42 (th: 405782)