5 ms·
If you're looking for exhausting your CPU cores, you can do it with Python as well, by using multiple processes, like when using "multiprocessing". What Python
by bad_user 10y ago
If you're looking for exhausting your CPU cores, you can do it with Python as well, by using multiple processes, like when using "multiprocessing". What Python and (currently) Ocaml won't do is being able to run in parallel multiple OS-managed threads that share the same address space. The approach in Ocaml is thus to use multiple processes instead of threads and do message passing between them and if you need to share some memory, you can do that too, although it gets tricky.
In terms of performance, inability to do in-process threads-based parallelism isn't Python's biggest problem though. Good throughput is generally achieved by avoiding indirection and by being able to control the memory layout. Because Ocaml is static, because Ocaml code isn't heavy on OOP and because Ocaml also gives you low level tools allowing you to control the memory layout, you can have code that's "as fast as C".
> Given that rust has picked up a lot in terms of typing and even syntax from OCaml, is OCaml still worth investigating?
Rust and Ocaml are very different programming languages. Rust does not have a GC and that changes its nature completely. For example Rust could be used for real-time systems where garbage collected languages would struggle. Another consequence for example is that Rust isn't a functional programming language.
- catnaroek 10y agoYou can do functional programming without garbage collection. The problem is social: few people in the functional programming community are interested in substructural types.
- bogomipz 10y agoCan you elaborate on how a FP language might be implemented without a garbage collector? How do things like closure/lambdas wok without GC? I tried to lookup "substructural types" but wasn't able to find anything.
- catnaroek 10y ago> I tried to lookup "substructural types" but wasn't able to find anything. A substructural type system lets you express constraints on how many times a value can be used. More precisely: (0) An object of an affine type cannot be used more than once. This is enforced by not allowing the same object to exist in multiple environments. Affinely typed objects can be moved (in the Rust sense) from one environment to another, but as soon as this happens, the object ceases to exist in the original environment. Affinely typed objects can be reclaimed as soon as they are first used (since they can't be used again) or go out of scope (since no other scope has access to them), so no dynamic garbage collection is necessary for them. (1) An object of a relevant type has to be used at least once. This is enforced by requiring the result of every computation to be fed into some other computation. In lazy languages, relevant types can be used for strictness optimizations: if the result of a computation is guaranteed to be used, then computing the result upfront eliminates the overhead of creating lazy suspensions. (2) An object of a linear type has to be used exactly once. A linear type is just a type that is both affine and relevant, so everything mentioned above applies. > How do things like closure/lambdas wok without GC? A closure has its own environment. With affine types, when a closure captures an object from the outer context, the object ceases to exist in the outer environment. This is how move closures already work in Rust. --- A digression: This is why I find GHC's plethora of type system extensions utterly disappointing. They marginally increase Haskell's expressiveness in areas where the language is already very expressive, but they don't address the language's limitations w.r.t. resource management. For instance, Haskell is incapable of statically enforcing that file handles won't leak.
- tome 10y ago> For instance, Haskell is incapable of statically enforcing that file handles won't leak. It is capable of it. You can do it with one monad transformer layer per file handle. Yes, that's utterly inconvenient, but you can do it. It would be far less inconvenient with (higher) row types.
- catnaroek 10y agoThat forces you to deallocate resources in the inverse order of allocation, which may or may not be what you want. For example, the lock acquisition and release pattern used in the implementation of Lehman-Yao trees (and variants thereof) is inexpressible using monadic regions.
- tome 10y agoHmm, well I've never implemented this myself so I'm not going to contradict you, but I'd be really surprised if you can't hoist into the monad transformer stack and close off a lower region earlier than the top-level region.
- catnaroek 10y agoWould this be safe if the destructor of an inner region requires objects from outer regions to still exist?
- tome 10y agoI don't see how there could be any such requirements, if we're talking about file handles.
- catnaroek 10y agoI'm pretty sure DBMS implementors have to work with data structures whose nodes are spread across several files, and which have to be locked and unlocked in very precise ways to preserve the integrity of the data they contain. I'd like to be able these locking and unlocking patterns with types.
- evincarofautumn 10y agoI’m writing a functional language without a GC, so maybe I can offer some insight. The basic reasons you want a GC are closures, as you mention, and reference cycles. In a pure, eagerly evaluated language, cycles aren’t possible, so naïve reference-counting is enough to reclaim all garbage. If the language has mutable reference types, you can still use RC, but you need a cycle detector. Closures are more interesting. A neat thing about purely functional languages is that you can’t observe whether something is a value or a reference, so there’s no difference (apart from performance) between copying and sharing. Purely functional data structures take advantage of this to share common parts of modified structures. For instance, when replacing a node in a tree, you produce a new tree, but all nodes are shared except the O(log n) nodes in the path from the root to the new node. What I do in my language project, then, is to always copy (or move) values into a closure. If they’re unboxed, they’re probably small, and if they’re boxed, they’re in a reference-counted or copy-on-write structure, so sharing is safe. The next thing you can do is to unbox closures (like C++ lambdas). You only need to box them (like C++ std::function) if you want them to have uniform size, for example if you’re storing them in a homogeneous list. Finally, you tie value lifetimes to scopes. When a value goes out of scope, you drop it and reclaim its memory immediately (or place it on a queue, for deferred RC). That’s where stack-based languages become really handy, part of the reason I’m writing one. That’s also where substructural types become interesting, because they let you encode the answer to the question “When am I allowed to copy or drop this value?” Rust augments this with “lifetimes”, which are essentially a static approximation of stack depth. For instance, you can talk about references to objects on the stack, but you can’t store a reference to a younger value into an older value, because the younger one will be reclaimed first—its lifetime is not a supertype of the destination’s. This prevents the problem of dangling references. I don’t feel like substructural types are really geared toward functional languages, however; where they really shine is safe imperative programming like Rust—for a particular definition of “safe”, anyway.
- catnaroek 10y agoAgree with most of this, and even upvoted it, but... > I don’t feel like substructural types are really geared toward functional languages, however; where they really shine is safe imperative programming like Rust—for a particular definition of “safe”, anyway. I disagree with this. Even vanilla Standard ML and Haskell can benefit from substructural types. The structural rules (weakening, contraction) don't have to be an all-or-nothing proposition. Why should I be forced to pick one between garbage collection for in-memory data structures (which Rust won't give me, at least not out of the box), and deterministic reclamation guarantees for file handles and network connections (which ML and Haskell won't give me)? I want both!
- nickpsecurity 10y agoThe basic way to do it is just to allocate and delete things without the safety of a GC. ;) PreScheme did that among other things: https://en.wikipedia.org/wiki/PreScheme https://en.wikipedia.org/wiki/PreScheme The medium approach might be using linear types like in ATS language: http://www.ats-lang.org/ http://www.ats-lang.org/ http://ats-lang.sourceforge.net/PAPER/LDD-plpv07.pdf http://ats-lang.sourceforge.net/PAPER/LDD-plpv07.pdf The heavyweight approach is integrating one with a theorem prover with C extraction like in COGENT: https://ts.data61.csiro.au/projects/TS/cogent.pml https://ts.data61.csiro.au/projects/TS/cogent.pml https://ts.data61.csiro.au/publications/nictaabstracts/8956.pdf https://ts.data61.csiro.au/publications/nictaabstracts/8956.... Hope these give you ideas of how it can be done with what benefits and effort.
- KirinDave 10y ago> If you're looking for exhausting your CPU cores, you can do it with Python as well, by using multiple processes, like when using "multiprocessing". Successful multiprocessing frameworks are, in a modern sense, indistinguishable from networked peer-to-peer systems. While this space is well explored, it's very difficult to call the resulting discipline "easy." Just like threaded programming, it's easy to make something subtly wrong. > Rust and Ocaml are very different programming languages. Rust does not have a GC and that changes its nature completely. For example Rust could be used for real-time systems where garbage collected languages would struggle. Surveying the field, real time garbage collection is not a pipe dream. It's a delivered and purchasable reality. Propagating the myth that only direct memory management can possibly deliver good results is ignoring the last 15 years of hard work on the subject, as well as products in the field. You're propagating a sort of urban legend. Please reconsider it.
- bad_user 10y agoHaving worked with real-time systems, it's far from being an urban legend. Please name "real-time" garbage collectors available for mainstream languages. And note that I'm not talking about ability to serve soft real-time services where you're allowed between 1 to 5% of all transactions to miss their deadline. Do that for a car break system or for an airplane guidance system and lives are lost. Heck, do that for video decoding and frames are dropped.
- nickpsecurity 10y agoThey make new ones on a regular basis with different properties. Most customers just buy the existing products from the commercial sector, though. Here's a few links to performance and embedded-oriented ones that support predictable pause times with one letting it go down to one, move instruction given pauses are interruptable. http://www.jopdesign.com/doc/nbgc.pdf http://www.jopdesign.com/doc/nbgc.pdf https://www.usenix.org/legacy/events/vee05/full_papers/p46-click.pdf https://www.usenix.org/legacy/events/vee05/full_papers/p46-c... http://www.cse.buffalo.edu/~lziarek/euro.pdf http://www.cse.buffalo.edu/~lziarek/euro.pdf http://www.aicas.com/papers/ismm02f-siebert.pdf http://www.aicas.com/papers/ismm02f-siebert.pdf So, there's some examples to work with. There's plenty more, too. Just search Google with terms "concurrent," "real-time," and "GC." Or "pauseless" replacing "concurrent." Or "hard" before "real-time." That combo is how I track them.
- spion 10y agoAFAIK OCaml probably has the best realtime GC.
- spion 10y agoCorrection: that would be low-latency GC. Realtime implies some guarantees, and I don't think OCaml's GC delivers that.