[HN Gopher] How Memory safety approaches speed up and slow down ...
___________________________________________________________________
How Memory safety approaches speed up and slow down development
velocity
Author : signa11
Score : 126 points
Date : 2023-01-17 08:06 UTC (14 hours ago)
(HTM) web link (verdagon.dev)
(TXT) w3m dump (verdagon.dev)
| amai wrote:
| ,,MMM (manual memory management) isn't really advisable for
| something exposed to the network."
|
| Nowadays basically everything is exposed to the network.
| phkahler wrote:
| >> As you can see, software development can be much more
| expensive than power usage, so it makes sense to primarily
| optimize for development velocity.
|
| No? Those developers can take time off, but the electricity can
| not. I think he showed how important optimizing software
| performance is at scale. When you electric bill is $3Billion, you
| gotta write efficient code, not write more code as fast as
| possible.
| manholio wrote:
| That example threw me off too because both the numbers and the
| perspective are non-nonsensical. 90% of the energy draw of
| those data-centers goes into things like inner video encoding
| loops, SSD/memcache storage and retrieval, ML algorithms etc.
|
| But the vast majority of those 27.000 engineers do not work on
| such low level routines, but on things like millions of lines
| of crufty Python that power Adsense analytics, which are
| essential for maintaining a revenue stream. Yes, development
| speed is very important but it's mostly orthogonal to other
| operational costs if the right tools and architectures are
| employed.
| verdagon wrote:
| Author here, that might actually make the comparison
| stronger: not all energy in a data center is wasted by the
| memory safety approach's drawbacks, meaning it makes less
| sense to optimize for memory safety overhead.
|
| But if I'm being pro-Rust, I would also say that not all
| coding is affected by a memory safety approach's downsides;
| there are some domains where the borrow checker doesn't slow
| development velocity down at all.
|
| Either way, I definitely agree that its orthogonal to many
| operational costs. I'll mention this line of thought in the
| article. Thanks!
| jmyeet wrote:
| So this particular problem is severely time-constrained.
| Performance doesn't relaly matter. In those situations you tend
| to use the highest level language you can. This isn't surprising.
|
| The author argues it's similar for startups. I agree to a point.
| For GC in particular I think the costs are underestimated.
| Processes blowing up because of memory leaks (which can still
| happen), dealing with stop-the-world ("STW") pauses and
| inconsistent performance are all real problems.
|
| So much of this is situational. For example, I think
| Objective-C/Swift was so successful on iOS (vs Java on Android)
| in part because it opts for reference counting instead of GC.
| This is predictable performance and less complicated. But RC
| isn't necessarily appropriate for a server as you may create hot
| spots and degrade performance that way.
|
| Rust by virtue of borrow checking isn't a panacea. It does
| however greatly reduce the chances of making a whole class of
| really important bugs (ie memory safety and all the entails like
| buffer overruns). Comparing it to Python, Go or Java doesn't
| necessarily make sense. Rust lives in the same domain as C/C++.
|
| One funny side note:
|
| > Google used 15.5 terawatt hours of electricity in 2020
|
| Bitcoin used 200 TWh in 2022 [1]. Think about that.
|
| [1]: https://www.cnet.com/personal-finance/crypto/bitcoin-
| mining-...
| FpUser wrote:
| I like how automatic memory management / safety in general
| implemented in Cone language.
| Ygg2 wrote:
| What does Cone do? First time hearing about it?
| verdagon wrote:
| Author here, Cone is a pretty fascinating language.
|
| It builds a borrow checker on top of any user-specified
| allocator, whether that be single ownership, reference
| counting, arenas, garbage collection, or any other user-
| specified strategy. A user can even code their own.
|
| It's a very promising approach, because it preserves the
| borrow checker's strengths while addressing its the borrow
| checker's development velocity downsides. GC and RC often
| make a program's overall architecture looser and more
| flexible, but by offering borrow checking for everything
| else, it allows a program to much more cut down on its memory
| safety overhead.
|
| What Cone does differently than other languages is that it
| decouples the allocation strategy from the type of data
| you're working with. This makes it much easier to change your
| code to use different memory safety styles. In a way, it's
| like Rust but allows for more flexibility on memory
| management.
|
| It's still in progress, but I encourage anyone interested in
| languages and memory safety to take a look:
| https://cone.jondgoodwin.com/
| zozbot234 wrote:
| User-specified local allocators are planned for Rust too.
| Among other things, they're needed for feature parity with
| C++.
| FpUser wrote:
| >"feature parity with C++"
|
| I think Rust is missing a fair bit more than "user-
| specified local allocators" to be on feature parity with
| C++. Curious if it plans to be on full feature parity.
| verdagon wrote:
| I recommend taking a look at Cone in a little more
| detail, its type system support for allocators are head
| and shoulders above what Rust has. The language's
| awareness of allocator allows it to decouple it from the
| users code in a way no other language allows (except
| perhaps Odin).
| comex wrote:
| Technically it's only the standard library that lacks
| custom allocator support. Box and Vec and all the rest
| are "just" types defined in the standard library, and if
| you define your own, you can use any allocator you want
| and still take advantage of the borrow checker and other
| language features. This has been possible since 1.0.
|
| ...Well, except for certain magic features that the
| standard library types get that you can't (yet) replicate
| in a custom type. This is an annoying wart, but they're
| relatively minor.
|
| But yeah, custom allocator support for standard library
| containers is planned.
| FpUser wrote:
| google cone programmer language. Short answer - your general
| computer language that is not ready for any practical use.
| H4ZB7 wrote:
| [flagged]
| zozbot234 wrote:
| Developer velocity is a crock. You want to write code that
| actually works over the entire software lifecycle, not just crank
| out piles of utterly broken sh!t that you'll have to fix later at
| huge cost. Rust has it right, and Ada/SPARK even more so.
| galangalalgol wrote:
| If you know algorithmically what will work sure, but if you
| don't, and you need to throw multiple approaches at a problem
| until one sticks, then a prototyping language is extremely
| helpful. The problem comes when management notices the
| prototype mostly works, they want you to ship it rather than
| make something sane now that you know which direction to head.
| I find using matlab is useful for this. Managers seem to
| understand that you don't ship matlab, where if I used numpy or
| julia...
| comex wrote:
| > In an average Google Earth quarter, only 3-5% of bug reports
| were traceable to memory safety problems, and Address Sanitizer
| made them trivial to reproduce in development mode.
|
| _Bug reports_ , sure. But as for _security vulnerabilities_ ,
| both Microsoft and Google have reported that roughly 70% of them
| were memory safety issues.
|
| Personally, as a Rust fan, I fully agree that the borrow checker
| limits developer velocity, and I'd be happy to toss it if I
| could. But verdagon has been promising memory safety with no
| overhead for years; I'll believe it when I see it.
|
| Edit: Okay, that was unfair to say without further context, so
| let me add two things. First, although Vale is open source, the
| design has changed over time and the implementation was described
| as a "work-in-progress" as of two months ago. [1] Second, a month
| before that, verdagon described the design as providing
| "statistical safety", meaning use-after-frees may fail to be
| caught but are caught "99.9999999999996% of the time". Now,
| stochastic exploit mitigations such as ASLR and PAC are valid
| defenses, as long as that 99...% also applies in the presence of
| an active attacker. But the specific design described (allowing
| generation counters to wrap) sounds like, in many scenarios, an
| attacker could manipulate it to provide no protection at all.
| There may be ways to mitigate this, but in context it does sound
| like it's designed under the assumption that memory safety
| violations will occur at random, rather than being designed to
| defend against attacks. This might be good enough for (some)
| games, but not for most Rust use cases.
|
| [1] https://verdagon.dev/blog/zero-cost-memory-safety-regions-
| ov...
|
| [2] https://verdagon.dev/blog/generational-references
| [deleted]
| revskill wrote:
| To me, my productive development process is like this:
|
| - Experimentation on the idea, algorithm, data structure,...,
| everything that actually produces some result.
|
| - Improve and finalize the specification of input, output,
| resource constraint, and eventually the high level architecture
| (modularity).
|
| - Refactoring, with best practices and patterns.
|
| - Benchmark usage and eventually swap out the implementation into
| safe languages.
| snovv_crash wrote:
| Memory isn't the only resource that needs management. File
| handles, network sockets, locks, security codes and more, all
| need to have deterministic lifetimes. If you language doesn't
| support lifetime management via some mechanism then all of these
| things are the next part that will bite, even if you have GC for
| memory.
| SigmundA wrote:
| In .Net/C# this is done with IDisposable and the using
| statement which works quite well.
|
| Ex: using (var sr = new StreamReader(filename))
| { txt = sr.ReadToEnd(); }
|
| https://learn.microsoft.com/en-us/dotnet/api/system.idisposa...
| pjmlp wrote:
| Modern C# version, using var sr = new
| StreamReader(filename); txt = sr.ReadToEnd();
|
| And for the _" what about forgeting to call using?"_,
|
| https://devblogs.microsoft.com/dotnet/infer-
| interprocedural-...
|
| No different from using all those static analisers in C and
| C++ to keep the code free of memory corruptions and UB
| issues.
| galangalalgol wrote:
| This gets missed a lot. Rust isn't memory safe, it is resource
| safe.
| snovv_crash wrote:
| It's funny that people are assuming I was talking about Rust
| when I was actually referring to modern C++. RAII is a
| powerful concept.
| galangalalgol wrote:
| It is, I've written c++ as my primary language for 30
| years. I skipped c++14 and I'm still using 17, but staying
| up to date other than that. But RAII doesn't come anywhere
| close to the borrow checker in the number of mistakes it
| prevents.
| hoseja wrote:
| Isn't it the other way around?
| galangalalgol wrote:
| My point is that it is both. The borrow-checker protects
| _every_ resource, not just memory.
| vlovich123 wrote:
| How so?
| kaba0 wrote:
| While I do love Rust-style resource management, Java's try-
| with-resources blocks plus cleaners are not particularly worse.
| amai wrote:
| Could a Java version of the future maybe also manage memory
| with a try-with-resources block ?
| flanked-evergl wrote:
| Java and the JVM has a garbage collector, and will likely
| always have, and GC makes memory management a lot simpler
| and safer, so while you could use try-with-resources for
| some kind of memory related resource, it likely would not
| be to make things safer, but to make things less safe and
| faster, and if you start doing that you will have
| significantly reduced safety compared to rust I think.
| kaba0 wrote:
| Your wish is granted :D
|
| https://openjdk.org/jeps/424
|
| Though there was nothing inherent to ever block you from
| doing it (just create an object that implements Closeable,
| and malloc in the constructor, free in the close method),
| this new API makes it quite great. The basic API gives you
| a SegmentScope which denotes a lifetime, and MemorySegments
| that point to a memory region (pointer+size+layout+scope).
| MemorySegments have both spatial and temporal safety, so
| accessing memory out of boundary will only throw an
| exception instead of corrupting memory, while any access
| outside the lifetime of the scope is forbidden. Oh, and
| they are also thread-safe, only optionally being shared
| between threads.
|
| So in practice it you can write something like
| try (var arena = Arena.openConfined()) {
| MemorySegment segment = arena.allocate(someLayout);
| // use segment } // memory will be deallocated here
| zozbot234 wrote:
| That's a nice improvement over sun.misc.Unsafe .
| amai wrote:
| Nice. Thank you for pointing that out.
| snovv_crash wrote:
| What about if you want your resource to be longer lived, but
| still safely disposed of in a timely manner when it goes out
| of scope, for example a port in a webserver class? Do you
| have to keep the stack alive here to keep the resource, or
| can you put it into an object somewhere to get a more RAII
| style resource management?
| pkolaczk wrote:
| Try-with-resources is tied to a lexical scope. C++/Rust RAII
| is not, you can move the resource e.g to another thread after
| initialization, and that thread can outlive the scope that
| created the resource. Also C++/Rust allows shared ownership
| through recounted references, something try-with-resources
| can't do.
| hawk_ wrote:
| Cleaners aren't deterministic, so that's meaningfully worse
| than rust's resource mgt. Java's try with resources cleanup
| only works if the resource is freed up before the callstack
| is unwound. Currently that's only viable for the most trivial
| of scenarios. Loom will make that applicable in more places
| but still not as widely applicable as a borrow checker/rc
| based deterministic resource management.
| pjmlp wrote:
| System languages with GC like D (one example among many others)
| support RAII for resource management.
| zamalek wrote:
| > Unsupported Patterns
|
| > Graphs; it generally only thrives with a strict tree hierarchy
| of data.
|
| This is actually now supported: https://doc.rust-
| lang.org/std/rc/struct.Rc.html#method.new_c...
|
| That being said, Rust has taught me that I was vastly overusing
| graphs (structurally, not algorithmically) in my code. Graphs are
| extremely difficult to reason about; a fact that I learned after
| approaching an unfamiliar Rust codebase for the first time. It
| was the brain equivalent of putting down a screwdriver and
| picking up a powertool, my mind just _relaxed._
|
| That alone is one reason I think that every developer should try
| and "suffer" under that constraint for at least one or two real
| problems (whether with Rust, or some other language). You'll end
| up writing better code in your preferred language.
|
| The removal of a feature is sometimes a feature. While roads
| limit where you can drive, they also mean you don't have to
| figure out how to traverse a mountain in a Lambo.
| Jensson wrote:
| That is how you write programs in competitive programming, just
| put all the data you need in a nice table structure and use
| indexes instead of references. It is very readable and easy to
| reason about for simple programs and is extremely fast to write
| and performant, that is how you are able to implement novel
| algorithms in minutes. But it doesn't scale, indexes aren't
| type checked nor do they tell you which collection they point
| towards.
|
| But yeah, knowing how to program like that is useful, learning
| new style will never hurt, but being forced to code like you
| code in competitive programming isn't a good thing for a
| language.
| warangal wrote:
| I write most of my code in Nim language, and it generally
| involves wrapping up some C codebase, or calling DLLs routines
| and working with raw pointers. Although Nim has it own GC or ARC
| to handle memory management for its native data-structures, i
| find it really helpful to define a `destructor` for any arbitrary
| objects/structs. Since language can track lifetime of any
| object/struct during compilation and can call corresponding
| `destructor` for that object, which generally is just a `free()`
| or `some dll exposed routine`. I end up using language to handle
| all the manual memory management even for existing C codebase.
| With new ARC, all destructor calls are inserted with compilation
| and hence no extra GC based runtime to deal with, and even works
| flawlessly with GPU programming stuff too ! I haven't used many
| programming languages, but personally find it really useful to
| use any language as an assistant to help solve issues outside of
| their intended context.
|
| I also have written about patterns i find useful when dealing
| with existing C codebases at
| https://ramanlabs.in/static/blog/raw_memory_management_with_...
| intelVISA wrote:
| Nim is a sleeper, plenty of GC options from ORC to manual and
| great C FFI.
|
| Wish the big corps had backed it, or Zig, over drab stuff like
| Go.
| mprovost wrote:
| This is a really interesting observation: "Stateless programs
| like command line tools or low-abstraction domains like embedded
| programming will have less friction with the borrow checker than
| domains with a lot of interconnected state like apps, stateful
| programs, or complex turn-based games."
|
| In my book [0] teaching Rust, I manage to completely avoid using
| lifetimes. Perhaps this is because it reimplements a command line
| utility (head, wc, cat, ...) from the original BSD sources in
| each chapter and none of them are stateful. Most of the classic
| Unix utilities just process streams of bytes and barely use data
| structures at all. Many don't even call malloc().
|
| It feels like Rust is a good match for the problem domain but I
| never made the connection with the stateless nature of these
| tools before. This bodes well for eventually replacing the
| coreutils with modern Rust implementations.
|
| [0] https://rftgu.rs/
| verdagon wrote:
| This was a particularly fascinating realization of mine a few
| years ago. I realized that for certain domains, the borrow
| checker was a dream to use (command line tools and scripts, ECS
| games), and in others it wasn't so much (GUI, turn-based
| games).
|
| I dove a little deeper, and the stateful/stateless distinction
| seemed to be the best rule-of-thumb for predicting how much one
| would be in conflict with the borrow checker's preferred
| styles. It was also reminiscent of functional programming
| languages, in a way.
|
| I suspect this is why most discussion comparing Rust to other
| languages devolves so quickly, and has become so polarized: it
| depends on the domain. Users who try to use it for some domains
| will hate it, and users who try it on other domains will love
| it.
|
| This is also why I recommend newer Rust programmers to not be
| afraid of using Rc/RefCell in certain domains. Some domains
| don't agree as much with the borrow checker, and Rc/RefCell can
| make the architecture a lot looser and easier to work with.
| Jensson wrote:
| > I suspect this is why most discussion comparing Rust to
| other languages devolves so quickly, and has become so
| polarized: it depends on the domain.
|
| The problem is that Rust proponents argues that Rust is
| better for all domains. I don't think anyone says that Rust
| doesn't have a place, the source of controversy is whether
| every low level programming task is best done in Rust or not.
| gavinray wrote:
| I made almost this EXACT comment a few days ago, nice to see
| I'm not crazy
|
| https://news.ycombinator.com/item?id=34386997
|
| For 98% of tasks Rust is incredible. But modeling those 2% of
| problems that don't fit neatly into Rusts domain, the level
| of "unsafe", "PhantomData", and advanced type-system hacks
| you need feels obscene when I can use some pointers in C++.
|
| I will openly say I think Rust is an all-around better
| language. If you are building general-purpose systems
| software, or just want to write fast software, use Rust.
|
| If you are fiddling with bits, doing low-level concurrency,
| writing a GC/Memory Manager etc, or have self-referential
| datastructures like graphs or trees, you're going to have an
| order of magnitude easier time writing it in C++. (Rust-
| experts excepted)
| planetis wrote:
| > Lobster is using borrowing and other static analysis techniques
| under the hood to eliminate a lot of reference counting overhead.
|
| Nim is one of the first languages to use borrow checking to
| prevent copies and ref counts, and it's done both explicitly with
| the experimental 'views' feature and when permitted implicitly.
| UltraViolence wrote:
| "Rust has made the decision that safety is more important than
| developer productivity. This is the right tradeoff to make in
| many situations -- like building code in an OS kernel, or for
| memory-constrained embedded systems -- but I don't think it's the
| right tradeoff in all cases, especially not in startups where
| velocity is crucial."
|
| I completely disagree with this statement. If a Rust developer
| takes longer to code some feature this time will eventually be
| saved fixing memory bugs later on. And don't forget the
| immaterial cost of losing customer confidence if the product
| crashes or glitches because of memory instability.
|
| And then I'm even glossing over the enormous benefit of Rust when
| writing correct multi-threaded code, which is almost always a
| minefield in C/C++. Code that looks and seems to work OK might in
| production suddenly crash after a year or so. A complete
| nightmare!
|
| Memory safety is ALWAYS a good thing to have.
| Tade0 wrote:
| My take is that it was always about expanding the pool of
| people capable of writing safe code, thus making it cheaper.
|
| I spent most of my career in front-end, where such
| considerations are way down the priority list, but even I was
| able to produce something in Rust that compiles.
|
| That being said I never understood why would anyone want to use
| this language for web development - most problems are solved in
| that space, so if you're not out to tackle tje unsolved ones,
| it might not be worth it.
| brabel wrote:
| > If a Rust developer takes longer to code some feature this
| time will eventually be saved fixing memory bugs later on.
|
| You don't disagree at all. "Later on" may never come to your
| start up , you're actually helping make that point for the
| author.
| roca wrote:
| I agree that GC languages are faster for prototyping, but this
| article downplays the disadvantages of GC. E.g. "deterministic
| destruction" is listed as a benefit of reference counting, but it
| really should be listed as a disadvantage of GC, since all the
| other memory-management methods have it.
|
| The "leaky abstractions" argument doesn't work for me. E.g. in a
| Java API you don't commit (at the language level) to the
| ownership of passed-in or returned mutable objects. In Rust, you
| do, and the author claims this is a problem. I say that in fact
| you depend on the ownership of the referenced object either way;
| Rust forces you to write down what it is and enforce that the
| callers honor it, while in Java you cannot. Arguably therefore
| Rust is _less_ leaky in that hidden assumptions are documented
| and enforced at the API layer.
|
| I've had lots of good experiences refactoring Rust code. It's
| probably more typing than other languages but the increased
| assurance that you didn't break the code (especially in the
| presence of parallelism) more than makes up for it.
| masklinn wrote:
| > The "leaky abstractions" argument doesn't work for me. E.g.
| in a Java API you don't commit (at the language level) to the
| ownership of passed-in or returned mutable objects.
|
| Yep, and so you get all sorts of bugs or defensive code
| everywhere. For that reason I also have a hard time with one
| section being
|
| > GC'd code can be more correct
|
| But apparently issues like iterator invalidation and other
| mutability conflicts don't rate.
| brabel wrote:
| The author mentions how experienced C developers use some
| techiniques that drastically improve the correctness of their
| code.
|
| The same goes with Java: we never use mutability in modern
| code bases anymore unless we have strong reasons to do so and
| can keep that mutability as constrained locally as possible.
|
| We also avoid inheritance these days, have your heard about
| that!? Because yeah, inheritance can be helpful but tends to
| bite you in the end.
| verdagon wrote:
| While technically true, I'm not sure that the iterator
| invalidation argument is that strong in practice. Rust
| doesn't seem to eliminate those problems, it more moves them
| around and transforms them into different (though sometimes
| easier) problems.
|
| We often think that the borrow checker protects us from these
| kinds of mutability conflict / single-threaded race
| conditions, by making it so we don't have a reference to an
| object while someone else might modify it. However, the
| classic workaround is to turn one of those references into an
| index/ID into a central collection. In real programming, that
| index/ID is equivalent to the reference. But while I hold
| that index/ID, the object can still change, which kind of
| throws the benefit into question.
|
| In other situations where we only have multiple read-only
| references, there's not really a motivating problem to begin
| with, even when coded in other languages that don't also
| regard them as read-only.
|
| I think the other benefits the article mentions (concurrency
| benefits, plus encouraging into flatter, cleaner
| architectures) are a bit more compelling.
| bsder wrote:
| > But apparently issues like iterator invalidation and other
| mutability conflicts don't rate.
|
| They don't, because you're using java.util.concurrent when
| you need data structures that deal with concurrency.
|
| I miss java.util.concurrent _terribly_ when I have to program
| in any other language. I don 't miss Java, but I weep every
| time I need the equivalent of ConcurrentHashMap or
| CopyOnWriteArrayList in another language.
| pornel wrote:
| I get it about Rust, because Rust really won't let you hide
| the fact that you're holding on to a temporary reference (GC
| is an abstraction over lifetime of objects). Borrow checking
| is also intentionally less flexible about getters (allowing
| library author to do anything in the getter, but that makes
| exclusive-mutable getters exclusive over the whole object not
| just one field). So Java programmers are in for a shock --
| public fields are encouraged, and APIs have to commit to a
| particular memory management strategy.
|
| But OTOH not having to worry about defensive programming is
| so great. Nothing is NULL. Nothing will unexpectedly mutate.
| Thread-safety is explicit. It's always clear when vectors and
| objects are passed, copied, or only briefly inspected.
| pjmlp wrote:
| Welcome to ML derived languages with tracing GC, specially
| pure ones.
|
| Java isn't the be all end all of GC based languages.
| moonchrome wrote:
| I'd think that if you're dealing with immutable data/pure
| functions GC becomes transparent even in Java ?
| pjmlp wrote:
| Yeah, however even with using Scala/Clojure, there is the
| possibility to touch parts of the standard library that
| fail to uphold such expectactions.
|
| So then one ends up with the usual issue of guest
| languages, where one needs to create wrappers for the
| platform APIs to keep the code idiomatic and language
| invariants.
| za3faran wrote:
| Even if you use a non-GC language, you are not guaranteed
| deterministic destruction (e.g. in data structures with loops
| or a highly variable number of nodes).
| pjmlp wrote:
| Because no two languages are implemented the same, and there
| are plenty languages with tracing GC that also support
| deterministic destruction.
|
| Unfortunely people keep reading only about Java and then they
| think they know something about GC based languages.
|
| Modula-3 and Mesa/Cedar are two examples from the past, D one
| from modern times, with many others in between.
| ben-schaaf wrote:
| Probably the same for the others; in D you have RAII but
| destruction is only deterministic for stack/static variables.
| There isn't deterministic destruction for heap allocated
| stuff unless you count manually freeing.
| pjmlp wrote:
| Except the little detail that manual heap allocation with
| RAII is also a thing.
| gumby wrote:
| That case isn't completely deterministic the way stack
| is: for the stack you just shift the SP which is constant
| time, while heap allocation and deallocation require
| manipulation of a small database, possibly including a
| system call.
|
| This is a bit of hair splitting I admit.
| pjmlp wrote:
| It is as deterministic as RC algorithms, which is usually
| the point being discussed.
|
| Yep, splitting hairs. :)
| ben-schaaf wrote:
| In which case you're no longer using the GC. You don't
| have GC & deterministic destruction, you have GC xor
| determinism.
| WalterBright wrote:
| D is not a gc based language. GC is just another tool D makes
| available to the programmer.
| gavinray wrote:
| For other folks who don't write D:
|
| D has GC, no GC ("@nogc" mode), or Ref-Counting
| (experimentally) with "@safe" and "@live" modes.
| pjmlp wrote:
| Which is exactly the point of GC enabled systems
| programming languages since the Xerox PARC days.
|
| Provide all the required features to write a full OS,
| while having the convenience of a GC for most of the
| code.
| pjmlp wrote:
| It is according to CS definition, and will keep being one
| unless @nogc is enabled by default for everything.
| WalterBright wrote:
| Using the GC in D is entirely at the user's discretion.
| You can also do functional programming in D, or not. The
| same with OOP, or not. Or RAII, or not.
|
| This is unlike Java, which forces the use of GC and OOP.
| Or Haskell which forces functional style.
| kaba0 wrote:
| Manual memory management does close-couple memory
| layout/ownership with the public API, and that will objectively
| make refactors regarding that layout breaking changes, causing
| many downstream changes required.
|
| A quote from the GC Handbook: "modules should not have to know
| the rules of the memory management game played by other
| modules"
|
| While Rust does make these refactors safe, it is nonetheless
| work that simply doesn't have to be done in case of Java for
| example.
| anonymoushn wrote:
| > The "leaky abstractions" argument doesn't work for me. E.g.
| in a Java API you don't commit (at the language level) to the
| ownership of passed-in or returned mutable objects. In Rust,
| you do, and the author claims this is a problem. I say that in
| fact you depend on the ownership of the referenced object
| either way; Rust forces you to write down what it is and
| enforce that the callers honor it, while in Java you cannot.
| Arguably therefore Rust is less leaky in that hidden
| assumptions are documented and enforced at the API layer.
|
| A lot of programs and large parts of a lot more programs just
| don't need these concepts at all and would run correctly with a
| GC that did not collect any garbage, memory permitting. Most
| Python code I write is in this genre.
|
| On the flip side, at work we mainly work on a low-latency
| networked application that has per-coroutine arenas created
| once at startup and long-term state created once at startup.
| The arenas are used mostly for deserializing messages into
| native format to apply to the long-term state and serializing
| messages to wire format. There's not much benefit to be had
| from writing all the time that everything that isn't on the
| stack belongs to the per-coroutine arena, except for all
| instances of like 3 structs, which belong to the long-term
| state.
| georgeecollins wrote:
| As an older dev who spent a lot of time with C on applications
| where performance and timing was key, I have found the timing
| of the overhead of GC hard to predict. So I tend to pre-
| allocate a lot of variables and structures in advance and hold
| them, so I don't have to worry about GC in a loop. If someone
| knows a good tutorial of how to predict when garbage collection
| will happen in my program, I would love to read it and learn
| more.
| UltraViolence wrote:
| I disagree. It depends on the type of project you're working
| on. If you're writing a public facing high-performance
| application it MAY be more beneficial to use deterministic
| memory management.
|
| Most tools, even very technical tools like assemblers,
| debuggers and compilers, don't need deterministic memory
| management. Only operating systems, device drivers and some
| time-critical applications (like video/audio encoding/decoding)
| need deterministic memory management.
| pkolaczk wrote:
| The trick is, when you have deterministic destruction you can
| use it for many more things than just memory management. E.g.
| need to add a metric to measure how many "somethings" are
| active - just attach a tracker to the struct representing
| that "something" and the tracker can count itself because it
| knows exactly when it is created and destroyed. We tried to
| add such metrics system to a Java app another day, and it
| turned out to be virtually impossible because due to GC
| nondeterminism we couldn't tell when most of the objects die.
| UltraViolence wrote:
| Do you really need to know? All that matters is that the GC
| will eventually clean up the used memory. When you're
| interested in that sort of info you're using the wrong
| language or asking the wrong kind of questions.
| pkolaczk wrote:
| Yes, of course I need to know. An important part of
| production grade software is observability. And that
| "something" doesn't have to be memory. It can be a
| connection, file handle, semaphore permit or one of many
| other things. But even for memory, typically there is not
| just one global type of memory. Users want to know how
| much memory is dedicated to X, Y and Z components, they
| might also want to constrain memory use by logical
| function. E.g separate buffer pools. This is where
| automatic memory management with GC falls apart very
| quickly - even the Java apps I work on actually use a
| memory/resource abstraction layer on top and manage
| everything manually, leaving just the last bit of
| releasing the objects to GC. Which is the worst of both
| worlds - we get all the disadvantages of MMM and all
| disadvantages of tracing (pauses, cache thrashing, etc).
| Jensson wrote:
| Java doesn't sound right for your use case. Most programs
| written in Java works just fine without tracking memory,
| and if you have any issues you do a jvm memory dump to
| see which object types are taking up all that memory and
| then look at why they weren't collected.
| pkolaczk wrote:
| Sure, I know that. But sometimes you don't have a choice
| because the decision was made years earlier by someone
| else, and you obviously won't rewrite a 1M LOC codebase.
|
| Nevertheless, after spending a year writing Rust, I'd
| take Rust over Java for just any use case now, even
| including CRUDs and GUIs (actually doing a GTK GUI for a
| Rust app right now, and it is not any worse in terms of
| dev speed than Java Swing was, despite what people say
| about callbacks - I really don't mind an occasional
| Rc/Refcell/clone here and there).
| jandrewrogers wrote:
| It comes up quite a lot in things like databases and
| high-performance data infrastructure generally. The
| instantaneous resource state is an important input
| parameter to many types of dynamic optimization. Ideally,
| you want to run close to the resource limits without
| exceeding them for maximum throughput and robustness
| while minimizing tail latencies. Knowing precisely how
| many resources are available allows the code to
| opportunistically apply that capacity to workload
| optimization without risking resource starvation. Some
| software will also adaptively switch entire strategies
| based on this information. It is very useful information
| if you are designing for performance.
|
| If you have no idea how many resources are actually
| available, you are forced to use conservative strategies
| to protect the process. Guessing or ignoring resource
| availability is an excellent way to greatly increase
| performance variability and reduce throughput.
| lelanthran wrote:
| >> need to [...] attach a tracker to the struct
| representing that "something" and the tracker can count
| itself because it knows exactly when it is created and
| destroyed.
|
| > Do you really need to know?
|
| He, uh, just said that. If you're tracking profiling
| metrics like he is (lifetime of a call-stack, maybe?),
| you _need_ to know.
|
| > When you're interested in that sort of info you're
| using the wrong language
|
| Under that argument, Java is the wrong language for
| everything.
| zozbot234 wrote:
| > in a Java API you don't commit (at the language level) to the
| ownership of passed-in or returned mutable objects. In Rust,
| you do[.]
|
| That's only the default, of course. Rc is commonly used in Rust
| to decouple ownership management from the simple use or passing
| of objects throughout the program, much like in any GC
| language. (Of course Rc on its own does not collect reference
| cycles; thus, "proper" tracing GC is now becoming available,
| with projects like Samsara https://redvice.org/2023/samsara-
| garbage-collector/ that try to leverage Rc for the most part,
| while efficiently tracing possibly-cyclical references.)
| galangalalgol wrote:
| It is more typing than julia or numpy maybe, but probably not
| more than c#, maybe not more than go, and most certainly not
| more than java.
|
| I think even within GC languages developer velocity varies a
| lot based on things like typing system, reflection, repl, and
| maybe interpreted vs compiled. My experience is that this
| variance is greater than what can be put at the feet of memory
| management
| kaba0 wrote:
| Java is much much less verbose than Go, and contrary to
| popular opinion, it really is not particularly verbose,
| especially modern Java.
| [deleted]
| galangalalgol wrote:
| I haven't written much go or much java (in this millennium
| anyway). The little go I wrote didn't have many error
| checks, is that what usually makes it verbose? The modern
| java I wrote was a little more functional than 1999 java,
| but it utilised reflection heavily, and it was a test
| harness for a black box, so not idiomatic probably, but
| there was still a lot of boilerplate. C# seems about normal
| verbosity, and so does rust. Both are less verbose than a
| lot of c++98 I run into because of lambdas and iterators
| etc. Julia on the other hand is quite possibly the least
| verbose language I have ever seen, apart from matlab (which
| isn't truly general purpose) , and python/numpy/pytorch
| close behind.
| jcparkyn wrote:
| > least verbose language I have ever seen
|
| You'd love APL.
| henry_viii wrote:
| Surprised to see no mention of V. I assumed it was using a model
| similar to Lobster?
| CGamesPlay wrote:
| One of the interesting things about MMM and borrow checking is
| that it enables you to implement GC and RC. Rust has RC built in
| of course, but the general consensus seems to be that it's "not
| idiomatic". There's also crates for GC. Would a Rust project that
| relied heavily on GC be able to sustain the sort of velocity that
| a project in a native-GC language had? Would it even be close?
|
| I love Rust's developer experience, frankly. The macros and build
| system are the main reason I like the language. I hate the borrow
| checker most of the time, for exactly the reasons listed in the
| article. It's probably a terrible choice for a 7DRL project,
| unless it was some sort of Rust core with content that was
| primarily orchestrated from another language (possibly DSL).
| kaba0 wrote:
| I'm not sure regarding the benefits of adding such a library-GC
| (that effectively does RC with cycle detection) to Rust. RC is
| just very slow and there is a very good reason that no
| performant managed runtime uses it. They are good in that they
| need no explicit support from the runtime, but that only makes
| them _possible_ , not performant.
|
| I would wager introducing a small rust hot loop via FFI into a
| managed language is a much saner route, using the respective
| languages to the best of their abilities.
| zozbot234 wrote:
| Obligate ARC is slow. RC/ARC as used in Rust (ARC separately
| marked, only used when required for thread safety; RC in
| general only for objects that may need to be kept around by
| multiple parts of the program, with no fixed "owner"; with
| simple RAII used for everything else) is very fast,
| comparable to manual memory management.
| kaba0 wrote:
| It can remove a few increments/decrements (more like a
| single pair at inlining a function), but that's not what
| makes it problematic.
|
| If you do have a bigger object graph, not using it anymore
| will recursively go on and decrement the counters, removing
| potentially many objects from memory, totally killing the
| given thread's cache/performance. All that is amortized
| with a tracing GC. On top, atomic counters are very
| expensive.
|
| Of course you can probably get away with using (A)RC at few
| places at most with Rust, but if you decide to track object
| lifetimes with RC for loads of objects, it will have a
| price (which may or may not be worthy. In my opinion, it is
| better to stick to Rust for low-level programs)
| CGamesPlay wrote:
| Makes sense from a performance standpoint. But what about
| development velocity? If the hypothesis is that wrestling
| with the borrow checker is a big drain on productivity, then
| bypassing it should restore that productivity.
| toast0 wrote:
| > I love Rust's developer experience, frankly. The macros and
| build system are the main reason I like the language
|
| On the contrary, I hate the macros, because the formater can't
| format inside tokio select or some of the other macros, and
| that means I have to do it manually, which is lame.
| CGamesPlay wrote:
| That's a pretty weak reason to hate macros, frankly. Seems
| more like a deficiency in the formatter than in the language
| feature.
|
| As a side note, there's a poorly-documented behavior where
| rustfmt will format macros that are wrapped in {} if it's
| valid rust code. So you can even point the blame on tokio for
| not using formatter-friendly macros.
| timidger wrote:
| I agree, that is a weak reason to dislike macros.
|
| I dislike them primarily because of the additional
| complexity they add that isn't always justified. All of the
| more complex macros I wrote I regret, especially the one I
| did at my last job since now others had to maintain it.
|
| Some macro use can be justified, eg serde. But I try to
| avoid using proc macros where I can (and certainly never
| try write one anymore)
| IshKebab wrote:
| Interesting (though clearly biased) article. I suppose we
| shouldn't be too surprised about the bias given the source.
|
| My main issue with it is the focus on _short term_ developer
| velocity. Fine if you are writing a game in a week and then
| stopping. Most people are not doing that.
|
| It has a great list of interesting new languages to look at!
| fuklief wrote:
| > In other words, CHERI can reduce memory-unsafety related
| slowdowns by two thirds, which is pretty incredible. AMD CPUs are
| even starting to have hardware support for it, bringing its run-
| time overhead down to 6.8%.
|
| Source on AMD CPUs having support for CHERI-style capabilities ?
| Afaik, there is only the Arm Morello prototype out right now and
| FPGAs.
| pjmlp wrote:
| Possible typo, the only known ones is ARM with the Monticello
| project.
| verdagon wrote:
| Author here, that was indeed a typo, fixed now. Many thanks
| for correcting that!
| neonsunset wrote:
| This article is biased in more than one way and misses a lot of
| nuance and aspects of GC-based languages which are not limited to
| pure GC and employ a lot of techniques for deterministic and
| semi-deterministic memory management, and various trade-offs to
| optimize for single/many-core and throughput/latency scenarios.
| In fact, many modern garbage collectors are not dissimilar to
| arena allocators making most of the allocations very cheap and
| efficient.
|
| In addition, Rust is difficult to use for iOS as it stands today
| but you can for sure make it support a variety of scenarios with
| delegates aka callbacks/function pointers/etc. Also RAII can be
| supported with .drop() and more. Half of the cons are incorrect
| and misrepresent current state of affairs.
|
| It also recommends defaulting to Go over C# for making a web
| server which my religious views compel me to publicly object to
| :)
| verdagon wrote:
| Author here, you're correct that there are a lot of performance
| tradeoffs involved with various GC'd languages, and also
| correct that GC'd languages do have some support for
| deterministic destruction (especially with defer).
|
| The article was trying to focus on the general approach of GC
| more than the GC'd languages themselves, and the article was
| already pretty long so I tried to keep it scoped to just
| developer velocity implications of the memory safety approaches
| themselves.
|
| I did mention some of those aspects, for example how C# is more
| than just a GC'd language (it has value types to help avoid
| GC), and Rust is more than just borrow checking (it has
| RefCell). But I couldn't get as deeply into these other aspects
| as I would like.
|
| (It's also pretty funny that the article's been called biased
| by both sides, in both directions now! Such is life.)
|
| I also don't mean to specifically recommend Go over C#, and I
| see the line that gives that impression, fixing now.
| littlestymaar wrote:
| I don't think this article is that interesting because it's
| really biased, and doesn't even try to catch any kind of nuance,
| especially regarding Rust.
|
| Developer velocity in Rust is a complex topic, and yes sometimes
| Rust will slow you down, but sometimes also it makes you move
| much faster. You can find a lots of testimonial in all
| directions, the majority of Rust users having a good opinion on
| Rust on that topic (but we're biased too). But somehow this
| article only chose to quote people complaining about developer
| velocity with the language and completely ignore the other (and
| larger) group of developers, praising the language user
| experience and development velocity.
|
| I don't think Rust is perfect, and I think that people talking
| about how "Rust is the most loved language on Stack overflow for
| the past N years" isn't really constructive. But a collection of
| cherry-picked quotes showing how Rust is in fact a disaster isn't
| bringing anything interesting to the discussion either...
| verdagon wrote:
| Author here, I tried to write this article to be as non-biased
| as possible, though it was pretty difficult.
|
| Most discussions and sources online compare Rust to easy
| targets, like C, C++, or Python, so we never get to really dive
| into the more interesting comparisons against stronger
| languages (especially GC'd ones). There are also some studies
| out there that try to measure Rust, but they measure the kinds
| of complexity that wouldn't translate to real-life coding.
|
| I actually like Rust (it's probably my favorite mainstream
| language today besides Scala), and if you consider all the
| dimensions together, there are a lot of situations where Rust
| is the best choice. However, this article is about one specific
| dimension (developer velocity) and on that, Rust unfortunately
| has some drawbacks.
|
| Also, the article does mention situations (concurrency) and
| aspects (encourages top-down, flattened architectures) that
| give Rust some advantages.
|
| I tried to be un-biased. Perhaps it didn't quite show, also
| since the conclusions recommended languages other than Rust
| when focusing on developer velocity.
| littlestymaar wrote:
| I mean, that's OK to express criticisms on Rust developer
| velocity (for instance, I totally agree that Rust doesn't
| particularly shine for prototyping) but then you've assembled
| from various parts of the internet a collection of TWENTY-ONE
| testimonials from people complaining about developers
| velocity in Rust, and _not a single one_ of people praising
| Rust for the productivity boost it offers, though I can
| definitely assure that there are, especially for refactoring
| where Rust is commonly considered[1] to be first class.
|
| Collecting pain point can be useful for the language, as it
| helps the Rust team to get a better vision of what they can
| improve, but doing such a one-sided list in an article about
| _comparison_ to other languages and memory handling mechanism
| is at best dubious.
|
| > Most discussions and sources online compare Rust to easy
| targets, like C, C++, or Python
|
| My background is mostly Java then JavaScript and you can
| quote me on the fact that I found Rust to be more productive
| than both of them in average, especially when the project
| last more than a few weeks. The biggest underlying reason is
| different though, JS mostly suffering from dynamic typing +
| overall questionable language and "standard library" design,
| while Java is plagued by inheritance and dubious OOP
| patterns. When using both of these languages after Rust I
| also felt frustrated by the impossibility to assign strong
| ownership to objects, making sure they aren't being watched
| or mutated behind my back. Also, billion dollar mistake.
|
| [1]:by rustaceans that is, it's not an objective measurement
| of any kind, but we're talking about testimonials here.
| verdagon wrote:
| Like I mentioned in response to your other comment about
| this, the anecdotes were added to the borrow checking
| sections because that's what the initial readers were
| surprised about. That in turn made those sections longer. I
| also did mention the benefits of borrow checking (such as
| the concurrency benefits and influencing us into cleaner
| architectures), plus the downsides of garbage collection.
|
| I didn't need any anecdotes for the borrow checker's
| benefits because everybody already knows them. I see how
| that can seem biased though, and next time I add citations
| to the surprising parts of an article I'll also add them
| more uniformly to the rest of the article as well.
|
| Also, the article was about garbage collection itself
| versus borrow checking itself, not any specific languages
| that use them.
|
| You make some valid comparisons between Rust and Java and
| Javascript, but garbage collected languages don't need to
| be dynamically typed, and don't need to have null, and
| don't need to have OOP patterns. When you compare Rust to a
| more modern language like Scala or Pony, you get a much
| truer comparison of the approaches.
|
| That's what the article is really comparing: borrow
| checking versus garbage collection. Not the extra features
| that are correlated with them in mainstream languages.
|
| Cheers!
| pjmlp wrote:
| Is biased and incorrent in a few places, like disregarding
| system programming languages with GC that also support
| deterministic destruction of resources.
| verdagon wrote:
| This article was mainly about memory safety approaches (GC,
| RC, borrow checking, etc) and their effects on development
| velocity. It wasn't trying to compare all languages along all
| dimensions. That would be quite a long article!
|
| Anyway, I think you're referring to `defer`? Though perhaps
| you're referring to something else, as defer also appears in
| non-GC'd languages as well so it didn't seem like a very
| relevant factor in this specific comparison. I do agree that
| defer (and try-with-resources) can be stellar tools for GC'd
| languages.
| pjmlp wrote:
| Nope, I am referring to C++/Ada style of RAII.
| littlestymaar wrote:
| I'm not surprised: when you read something and the part you
| know about is bullshit, there's a good chance that the part
| you don't know about is also bullshit even you can't see it.
|
| What language have both GC and deterministic destructors, and
| how does it works? I'd guess it couldn't be tracing GC
| (because the GC runs whenever it wants, so you don't have
| determinism, or at least that's my understanding). Or maybe
| it's something like RAII for non-memory resources (with a
| destructor that doesn't free memory), and a GC for memory
| management?
| pjmlp wrote:
| Mesa/Cedar, Modula-3, Active Oberon, D, Nim, for example,
| not to make an exhaustive list. C# also does provide some
| mechanisms to dive into it, since version 7, although not
| as easy.
|
| Basically you can use GC heap as usual, or native heap with
| untraced references, stack or global memory segments.
|
| Value types with constructors/destructors pairs are also
| supported.
|
| So you have all the building blocks to do C++ style RAII
| when needed, otherwise relax and enjoy the GC.
| littlestymaar wrote:
| thanks
| UltraViolence wrote:
| I believe Rust will ALWAYS be beneficial compared to MMM
| languages because it will save time in the long run.
|
| Programs with Rust will always be rock-stable, unlike many
| C/C++ programs which are more like a house-of-cards.
| zamalek wrote:
| The borrow checker only prevents certain classes of memory
| bugs. You can definitely still have logical bugs in Rust.
| brabel wrote:
| > Programs with Rust will always be rock-stable
|
| How do you know that? Is there any data backing that up?
|
| I've never seen any research showing that a programming
| language, no matter how strict (Haskell, Ada, Rust) actually
| improves the reliability of software, except for comparisons
| between memory-safe and non-memory-safe languages. It almost
| always goes down nearly entirely to development process and
| team skills/experience, showing anything else convincingly
| would be a huge breakthrough.
| manholio wrote:
| Well, here's my point of anecdata: despite Python being a
| very productive language and very easy to use, despite it
| existing for more than three decades and being perhaps the
| most massively learned language by aspiring programmers
| today, you hardly see any stand-alone application written
| in it. I can name not a single application that I use on a
| daily basis written in Python. It seems it's strongly
| confined to the web-server, where the environments are well
| controlled via containers, runtime bugs impact a single
| page load and fixes can be applied continuously. People
| don't ship Python standalone apps.
|
| Based on the buggy and unstable Python desktop apps I have
| used, I have a strong suspicion that developing large
| applications in Python is strongly self-limiting after the
| initial sprint.
| UltraViolence wrote:
| I'm speaking from my own experience here. I've tested a
| couple of Rust programs which were developed in relatively
| short order yet were quite complex (a Minecraft type game
| being one) and there wasn't even the slightest hint of
| instability.
|
| Some of my own Rust code is moderately complex but never
| showed any signs of instability during development. I often
| have crashes now and again with my C++ programs. Sure, I
| fix those afterwards but getting it flawless every time the
| first time is (for me at least) unheard of.
| littlestymaar wrote:
| I'd agree with that too, but that's just my point of view and
| I'd respect anyone else's _sensible_ argument of the
| opposite.
|
| But this article isn't that, and to make manual memory
| management more appealing they had to ridiculously inflate
| the issues that come with ownership-based memory
| management...
| brabel wrote:
| I think it's you being biased against the article now. As I
| read it, it was pretty honest and really trying to find
| some sort of true, not just trying to bash Rust. Feels like
| Rust fans are very defensive against criticism of any sort,
| even when well intentioned as I think is the case here.
| littlestymaar wrote:
| Come on, there's literally 21 citations[1] of people
| saying Rust slows them down, and ZERO citation of the
| opposite.
|
| There's not trying to hide that they are biaised, at this
| point it's Kremlin-level of shameless bad faith.
|
| [1] it looks like they've harvested Rust criticism for an
| entire year at this point, since they end up even quoting
| random discord comment from more than a year ago: https:/
| /discord.com/channels/273534239310479360/818964227783...
| verdagon wrote:
| Author here, I tried to make this article as balanced as
| possible, and even talked about Rust's advantages in
| concurrency and encouraging cleaner architectures and the
| disadvantages of MMM and GC approaches (plus other
| aspects, see my other comment down-thread about this).
|
| There are actually 45 citations in the article on all
| angles, but I think you're talking specifically about the
| anecdotes.
|
| Regarding the anecdotes, I had to add more of those to
| the borrow checking sections because it was the most
| surprising to my initial readers. Very little discussion
| online actually compares borrow checking to higher-level
| languages with good development velocity; most discussion
| online compares it to languages like C, C++, Javascript,
| or Python, so this was new to most readers.
|
| The article also explicitly mentioned that those were
| anecdotes and colored them differently, so that people
| didn't mistake them as data.
|
| They also made that part of the article much longer than
| it was originally.
|
| I can see how that could come across as biased. Perhaps I
| should have added citations to the other parts of the
| article so their distribution was more uniform.
|
| When you look at the content itself, it's pretty balanced
| I'd say (hence the focusing on the other benefits of
| borrow checking plus the downsides of GC), it's
| unfortunate that's not coming through as much.
| littlestymaar wrote:
| Yeah, the issue is that you've given so much room for
| critical citations that your article is basically a
| pamphlet at that point.
| UltraViolence wrote:
| It may slow them down during their initial development
| when they're new to Rust, but I'm convinced that it will
| be much cheaper during the entire Application Lifecycle
| (ALM), not to mention customer satisfaction.
___________________________________________________________________
(page generated 2023-01-17 23:02 UTC)