Can somebody please explain or give me some links how FP is supposed to work without a GC? For example Rust has different types of function pointers (Fn, FnMut, FnOnce), to guarantee the possibility of lifetime analysis (so it is arguable to consider it a functional programming language). On the other hand the most common FP languages (OCaml, Haskell) all come with a GC. Or am I wrong in assuming this is a functional…
As long as you forbid or mark cycles (…or ignore the problem) lifetime analysis can be done statically. Which is better anyway. Automatic memory management doesn't need a GC.
Carp – A statically typed Lisp, without a GC, for real-time applications
61–70 of 139 posts
Re: Carp – A statically typed Lisp, without a GC, for real-time applications
#62Earlier quoted context omitted.
Not an expert at all here, just a few thoughts and experiences. I think "forward guarantees" (meaning "lock-free"?) can be implemented anywhere. Non-blocking IO can still be slow in my experience, for example a write() syscall on Linux to a non-blocking TCP socket on Linux sometimes took dozens of milliseconds when I measured. I don't have enough experience to know if one can get better timing guarantees with tuning…
lock free does not guarantee all participants have a fair treatment, e.g. one thread can keep losing the CAS (CompareAndSwap / LoadLinked/StoreConditional / etc.) and effectively spinning or need to yield. releasing the CPU resources. A non-blocking write should just copy the data to the socket buffer, definitely not taking milliseconds. Allocate buffers in separate threads - ok, that's the crux of it - the memory is…
That's the theory, right? It could be I measured something wrong, but sometimes dozens of ms is what I got, and I concluded that non-blocking I/O avoids indeterminate blocking (such as reading from a TCP socket until the sender sent N bytes) but does it completely avoid taking in-kernel locks etc? Probably not.
I didn't find any other measurements on the internet, please point me to them if you find them.
Thinking back again, another explanation could be false sharing effects that I didn't know well at the time.
> Allocate buffers in separate threads - ok, that's the crux of it - the memory is shared amongst the threads within a process, so the allocator has to be non-blocking as well or the large allocation would prevent allocations in other threads.
I've written a SRSW lock-free ringbuffer for example. The ringbuffer memory is allocated at startup. The cost to allocate from this ringbuffer is very little. It's probably not super important, but the ringbuffer even caches the read or write pointer from the other thread, so it only needs a cache line transfer when the cached value is not sufficient to accomodate the read or write.
This is way outside my experience, but I think it should be possible to do some similar stuff with multiple readers and/or writers? I think you should still be able to allocate from a ringbuffer without live-locking (unless the buffer is full of course, in which case events are dropped anyway).
> Next release memory to the OS (or unmapping memory mapped files) - mumnmap requires TLB flush for all cores assigned to the process.
I think most programs do only "static" allocation at program startup. There's no OS interaction for memory management from then on.
> Normally GCs trigger only at 'safe points' and unless they need to allocate more memory (should never be the case for a real-time application), GCs should have no OS interaction.
Ok, if the GC is configured to never return memory to the OS, it makes sense that probably the GC in an event handling system won't need to get more memory from the OS, or only rarely, once it is "warm".
Still, GCs are generic systems that have to be less efficient than specialized systems. And what are 'safe points'? I'd rather decide myself. I think a GC trace of <1ms, as some new GC allegedly do (is this widely acknowledged? I would assume it depends a lot on the allocation patterns / granularity etc), could be enough, but I'd rather control this myself (for the somewhat real-time app that I've been working on, I need << 10ms latency).
Re: Carp – A statically typed Lisp, without a GC, for real-time applications
#63Earlier quoted context omitted.
From the Carp docs: Memory management is handled by static analysis, a value is owned by the function where it was created. When a value is returned or passed to another function the initial function will give up ownership of it and any subsequent use will lead to a compiler error. To temporarily lend a value to another function (for example to print it) a reference must be created, using the ref special form (or the…
So how is memory fragmentation avoided?
(Quite often the answer is "it isn't")
Re: Carp – A statically typed Lisp, without a GC, for real-time applications
#64Earlier quoted context omitted.
AOT doesn't necessarily mean GC-less, right?
Maybe they actually mean VM-less, which would probably imply GC-less because if you have a GC, you pretty much have a VM doing the GC (unless it's simple ref-count). For embedded, you need VM-less, I think, as VMs tend to be too heavy.
Re: Carp – A statically typed Lisp, without a GC, for real-time applications
#65Earlier quoted context omitted.
Janet is dynamic with a garbage collector. Carp is a GC-less static Lisp with deterministic memory allocation via a Rust-like borrow checker. I only wish Carp had stayed nearer Clojure's syntax. A more familiar syntax would make it more accessible.
As someone who doesn’t know any lisp well, it’s odd to hear you highlighting significant differences in syntax between them. I thought the character of a lisp is that it doesn’t have much syntax. I’d be interested in an example.
Re: Carp – A statically typed Lisp, without a GC, for real-time applications
#66Earlier quoted context omitted.
That is also the case in Carp, x being an expression here.
Ah, okay. So is there a way to declare that a variable will always be of a given type in Carp?
You can also annotate function with a type signature.
(sig add (Fn [Int Int] Int))
(defn add [x y] (+ x y))
Is the same as: (defn add [x y] (the Int (+ (the Int x) (the Int y))))Re: Carp – A statically typed Lisp, without a GC, for real-time applications
#67Re: Carp – A statically typed Lisp, without a GC, for real-time applications
#68Earlier quoted context omitted.
lock free does not guarantee all participants have a fair treatment, e.g. one thread can keep losing the CAS (CompareAndSwap / LoadLinked/StoreConditional / etc.) and effectively spinning or need to yield. releasing the CPU resources. A non-blocking write should just copy the data to the socket buffer, definitely not taking milliseconds. Allocate buffers in separate threads - ok, that's the crux of it - the memory is…
> A non-blocking write should just copy the data to the socket buffer, definitely not taking milliseconds. That's the theory, right? It could be I measured something wrong, but sometimes dozens of ms is what I got, and I concluded that non-blocking I/O avoids indeterminate blocking (such as reading from a TCP socket until the sender sent N bytes) but does it completely avoid taking in-kernel locks etc? Probably not.…
It has been years since I have written thousands sockets servers (used in forex), yet even a couple milliseconds per write would have made the entire operation useless.
>another explanation could be false sharing effects that I didn't know well at the time.
False sharing sucks, of course, but milliseconds seems way way too much again penalty. You'd need all cores updating the same cache line, even then I doubt it'd be that bad.
Could it be the virtualization layer? (again I have run 'that' on bare metal only)
>I think it should be possible to do some similar stuff with multiple readers and/or writers
Multiple writes should be avoided entirely, contention on writing is what prevents scaling - false sharing is pretty much that. A writer honoring the readers is pretty simple - each reader has its own pointer and the write should fail (or spin) when the buffer is full. I think that's quite a classic structure and indeed - it requires no extra memory to communicate/hand-off.
About latency - this is java's current GC that does still has a compaction phase. https://malloc.se/blog/zgc-jdk16
Re: Carp – A statically typed Lisp, without a GC, for real-time applications
#69For the history buffs: Pre-Scheme: A Scheme Dialect for Systems Programming Richard A. Kelsey, 1997 Abstract Pre-Scheme is a statically typed dialect of Scheme that gives the programmer the eciency and low-level machine access of C while retaining many of the desirable features of Scheme. The PreScheme compiler makes use of type inference, partial evaluation and Scheme and Lisp compiler technology to compile the pro…
Re: Carp – A statically typed Lisp, without a GC, for real-time applications
#70I'm not really familiar with lisps so this might be a documentation oversight.
Looks really neat overall.