Hacker Newsnew | past | comments | ask | show | jobs | submit | nu11ptr's commentslogin

What a blast from the past. I recall my own experimenting with DJGPP and DPMI in my own software. It felt futuristic at the time. I was blown away.

Another fond memory: I was playing Star Wars: Dark Forces (I think that was the one) and was frustrated with the speed of level loading. I think it used DOS/4GW and I recall renaming it, copying in a new dos extender (was it CWSDPMI? not sure), and renaming it to the one Star Wars used. I was shocked when it not only worked, but the level loading was MUCH faster (like 3-5x faster I recall). My guess is whatever one I had swapped in wasn't calling the interrupt in DOS (by swapping back to real mode), but perhaps was calling the IDE disk hw directly from protected mode. Not sure, but it was a ton faster, and I was a very happy kid. The rest of the game had the same performance (which makes sense I think) with the new extender.


I was curious so I googled. Best I can tell it is still supported, but perhaps stagnant?

IMO, C# is just a somewhat better version of Java (low bar) w/ first class Windows API support. I can't see myself ever adopting it for any real work. F# on the other hand seems a pretty awesome, terse and expressive language. Unfortunately, it is very unpopular (yet still hangs around).

> I can't see myself ever adopting it for any real work

Why not?

I see a lot of people saying they don't like it or won't use it but few of them list any reasons to, the ones that do raise issues from 20 years ago that have since been resolved.

Maybe you should give it a try, you'd be surprised how productive the language is and how comparatively unproductive all other languages and ecosystems are.


I have tried it recently. It felt like I was coding Java. I realize "feel" isn't very objective, but I don't really like C-like languages in general. I like Rust, but only because of it's heavier FP-influence. C# is one of those languages where you don't mean to, but you can't help just creating tons of files/boilerplate/etc., which feels just like Java. Just my perspective, others obviously feel differently.

You can (and could have for a while now) just use Avalonia natively. It supports Windows/Mac/Linux/WASM natively already. I think this work is primarily for those who already have MAUI apps and want to target Linux/WASM from an existing codebase.

I admit it may just be because I'm a PL nerd, but I thought it was general knowledge that pretty much EVERYTHING in Python is an object, and an object in Python is always heap allocated AFAIK. This goes deeper than just integers. Things most think of as declarative (like classes, modules, etc.) are also objects, etc. etc. It is both the best thing (for dynamism and fun/tinkering) and worst thing (performance optimization) about Python.

If you've never done it, I recommend using the `dir` function in a REPL, finding interesting things inside your objects, do `dir` on those, and keep the recursion going. It is a very eye opening experience as to just how deep the objects in Python go.


Heap allocation does also allow other things. For example stack frames (PyFrame) are also heap allocated. When there is an exception, the C stack gets unwound but the heap allocated frames are retained forming the traceback. You can then examine the values of the local variables at each level of the traceback.

And it also allows async functions, since state is held off the C stack, so frames can be easily switched when returning to the event loop.

The other thing made easy is C extension authoring. You compile CPython without free lists and an address sanitizer, and getting reference counting wrong shows up.


Yeah, spelunking around this is a great way to learn a language!

That said, “is an object” is a bit of an overloaded term in this context. Most things in Python can be semantically addressed as objects (they have fields and methods, “dir” interrogates them, and so on), but not everything is necessarily backed by the same kind of heap-allocated memory object. Some builtins and bytecode-level optimizations, similar to (but not quite the same as) the ones discussed in TFA, result in things being less object-like (that is, PyObject*-ish structs with refcounting and/or pointer chasing on field access) in some cases.

And that’s an important difference, not a nitpick! It means that performance-interested folks can’t always use the same intuition for allocation/access behavior that they do when determining what fields are available on a syntactic object.

(And before the inevitable “if you want performance don’t write Python/use native libraries” folks pipe up: people still regularly have to care about making plain Python code fast. You might wish it weren’t so, but it is.)


> but not everything is necessarily backed by the same kind of heap-allocated memory object.

Do you have an example, I thought literally everything in Python did trace to a PyObject*.


Yeah, I probably should have been careful there. Good call out that not every "object" is necessarily a heap allocated entity. I learned something, thx.


Quite frankly, as someone with 20+ years of experience I have no idea what the basis is for what you're saying here.

> not everything is necessarily backed by the same kind of heap-allocated memory object.

`None`, `True` and `False` are; integers are; functions, bound methods, properties, classes and modules are... what sort of "everything" do you have in mind? Primitive objects don't get stored in a special way that can avoid the per-object memory overhead, and such objects exist for things that can't be used as values at all (e.g. passed to or returned from a function) in many other languages.

Some use fields like `tp_slots` in special ways; the underlying implementation of their methods is in C and the method attributes aren't looked up in the normal way. But there is still a PyObject* there.

... On further investigation (considering the values reported by `id`) I suppose that (at least in modern versions) the pre-created objects may be in static storage... ? Perhaps that has something to do with the implementation of https://peps.python.org/pep-0683/ ?

> Some builtins and bytecode-level optimizations

String concatenation may mutate a string object that's presented to Python as immutable, so long as there aren't other references to it. But it's still a heap-allocated object. Similarly, Python pre-allocates objects for small integer values, but they're still stored on the heap. Very short strings may, along with those integers, go in a special memory pool, but that pool is still on the heap and the allocation still contains all the standard PyObject fields.

> people still regularly have to care about making plain Python code fast.

Can you give examples of the people involved, or explain why their use cases are not satisfied by native libraries?


> what sort of "everything" do you have in mind?

I’ll answer in two parts.

First, in my original claim:

> not everything is necessarily backed by the same kind of heap-allocated memory object.

the emphasized part is important. Not all PyObjects are equivalent. Most things in Python are indeed PyObjects of some stripe, but they act very, very differently in some common and performance-sensitive situations. The optimizations discussed in the article are one example: allocations can be re-used, and some more complex data structures are interned/cached (like the short-tuple/short-list optimization, which is similar but not the same to how floats/numbers are reused via allocator pools: https://rushter.com/blog/python-lists-and-tuples/). As you point out, immortal objects and fully preallocated objects are also often special cases vis-a-vis the performance of creating/discarding objects.

Second, you’re wrong. PyObjects do not come into play in a wide variety of cases:

- In some (non-default even today, I believe) interpreter configurations, tagged pointers are used for some numbers, removing pointer chasing entirely: https://rushter.com/blog/python-lists-and-tuples/

- Bound method objects can be cached or bypassed, meaning that, while there’s a PyObject for the object being method-called on and the method, there’s sometimes one less PyObject for the binding: https://github.com/python/cpython/issues/70298, with additional/somewhat related info in PEP-580 and PEP-590. PyPy improves on this further: https://doc.pypy.org/en/latest/interpreter-optimizations.htm...

- None, False, and other “existential” types are, as you say, PyObjects. But they’re often not accessed as PyObjects and have much lower overhead when used in some cases. Even beyond the performance savings of “is” comparing by address (technically by id(), but that maps to address in CPython), special bytecode instructions like POP_JUMP_IF_NOT_NONE are used to not even introspect the "None" PyObject at all during comparison on some paths. Compare the “dis” output for an “x is None” check versus “x is object()” check to see the tip of the iceberg here.

- New/rolling out features like JIT compilation and tail-call optimization further invalidate the historical wisdom to consider everything an object: calling a function may not create PyObjects for argument stacks; accessing known-unchanged object fields may cache accessor results, and so on. But that’s all very new/not released yet; this isn’t meant as a “gotcha” to disprove your claim alone.

- While-True/While-1 loops don’t load or interact with the always-truthy constant value’s PyObject at all while they run, in many cases: https://github.com/python/cpython/blob/main/Lib/test/test_pe...

- Constant folding (which happens at a higher level than interning and allocation caching) deduplicates pyobjects, allowing identity/equality caches to be used more frequently, and invalidating some of the historical advice to consider nonscalar data structure comparison arbitrarily expensive.

- Things like LOAD_ATTR (field/method accessors) retain their own caches, invalidating the wisdom that “obj.thing” always pays a pointer-chasing cost or a binding-creating cost. In many (I’d go as far as guessing it’s “most”, though I don’t have data) looping cases, attribute access on builtins/slotted classes is always returning attributes from the cache without addressing any PyObject fields deeper than a single pointer. That's very different from the overhead of deep diving through the MRO to make a lookup. Is it still a PyObject and still on the heap? Sure! But are you paying the same performance cost as accessing uncached/seven-pointers-away heap data? That answer is much less cut and dried.

- Many many more examples I did not think of off the cuff.

The usual caveats apply: the above applies largely to CPython, and optimization behavior can and will change between interpreter versions.

> Can you give examples of the people involved, or explain why their use cases are not satisfied by native libraries?

I mean, the most significant fact in support of the claim is that the above optimizations exist. They were made in response to identified need. Even if you take a highly uncharitable view of CPython maintainers’ priorities, the volume of optimization work speaks to real value delivered by making pure python fast.

Beyond that, anecdotally, everywhere I’ve worked on Python--big SaaS platforms, million+ RPS distributed job/queueing systems, scientific research projects--over the last 15 years (hello fellow veteran!) has routinely had engineers who needed to optimize pure-python ops as part of ordinary, mundane, day-job tasks.

Even once you remove optimizations they performed that would have been necessary in any language (stop copying large data objects, aggregate I/O, and so on), I’ve worked with … hell, probably hundreds of engineers at this point that occasionally needed to do work like “okay, I’m creating tons of tuples in a loop and it’s slow, how can I make sure to recycle/resize things such that the interning cache handles more of my code?”. Silly tricks like "make some of the tuples longer than they need to be so that length-based interning has cache hits for individual items" are sometimes useful and necessary! Yes, that's seriously funky code and should not be the first resort for anyone (and merits a significant comment/tests re: validating behavior on future interpreters), but sometimes those things produce point fixes which yield multiple percentage points of improvement on memory/GC pressure without introducing new libraries or more pervasive refactors. That's not nothing!

Sure, many of those cases would have been faster if numpy or another native-code library were in play. But lots of that code didn’t have or need numpy/extensions for anything else, including it would (depending on how long ago we’re talking) have required arduous installation/compatibility acrobatics, sometimes third-party modules are difficult to get approved, and, well, optimization needs were routinely met by applying a little knowledge of CPython’s optimizations anyway, so I’d say optimizing pure Python is both a valid approach and a common need.


Correction on my first bullet, regarding tagged pointers. The link was wrong (mistaken duplication of the link used above), and the tagged pointer optimization is not present in CPython, but only in PyPy, so it largely does not pertain to this discussion: https://doc.pypy.org/en/latest/config/objspace.std.withsmall...

> I recommend using the `dir` function in a REPL

A while back I wrote this https://mohamed.computer/posts/python-internals-cpython-byte..., perhaps it's interesting for people who use `dir` and wonder what some of the weird things that show up are.


I love Cursor. I've tried Copilot/Claude/etc. but keep coming back to Cursor. I just want to work, and Cursor tab complete is dang accurate, esp. for refactoring tasks.


I tried going back to VS Code + Copilot a month ago. I only lasted 4 days because it was to bad. It was super slow and gave poor suggestions, but mostly it just flat out did not suggest anything. Cursor feels snappy in comparison and the suggestions are more often than not useful. The most annoying thing about Cursor tab complete, is that it is so fast that when I am doing something unusual then it will keep on jumping in with useless suggestions. They have a snooze function for this though.


Damn TIL, I always used > Cursor: disable completions and forgot to turn it on again I need to try snooze then!


This looks great too! Hopefully one or more of these component libraries catch on and stay well maintained.


Are any pieces of this open source by chance? (other than iced itself)


Unfortunately not


I think this is pretty common on Linux. You would want to GTK (or Qt) I would think to draw the top level window and perhaps system menus, etc. even though the UI itself is drawn using a GPU canvas.


> You would want to GTK (or Qt) I would think to draw the top level window and perhaps system menus, etc. even though the UI itself is drawn using a GPU canvas.

No, you would want to draw for Wayland or X. GTK and Qt themselves don't burden with importing each-other to work, for example.

My guess is that they import GTK only to get a title bar on GNOME, as GNOME forces applications to render their own. They could go custom and cut the dependency but it never looks quite right when apps do that.


> They could go custom and cut the dependency but it never looks quite right when apps do that.

This is literally what the GNOME devs advocate: that each application draw their own borders and titles. You might consider that it doesn't look quite right, but that's the design choice they're going with.


No. On Wayland all of that should be in the compositor. Window sizing and positioning can not be done by the apps, so it makes sense that the controls for that are drawn and handled by the WM. But Gnomes gotta gnome...


Are GTK/Qt memory safe now?


No. What is the likelihood of an attack on a desktop program via memory unsafety?


Really high?

What do you run your browser as?


Browser runs complex untrusted code from the internet. Most desktop programs don't do anything like that. The servo programmers were riding a motorbike. Using Rust for a desktop program would be more like wearing a crash helmet in a car.


>What is the likelihood of an attack on a desktop program via memory unsafety?

Low.

What's the likelihood of someone entering your house if it's unlocked?

Also low, and yet you lock it.


A desktop program is already in a locked house - your desktop - which I can't login to.


Great.

You answered your own question, then.


Do all desktop programs only ever run in "your house"?


Even though most UI libraries now draw their own widgets some native integration is almost always used/desired. Those integrations are typically: keyboard short cuts, native system menu (macOS), native file dialogs, and (sometimes) native context menus. I'm sure there are others I'm forgetting, but these minimal integrations are a good thing as they give the user some sense of familiarity.


Not just a sense of familiarity; you will simply never build the full spectrum of a file explorer's functionality in a custom file dialog, that would be a complete waste of engineering time. And many more users than you'd expect benefit from the fact that native file dialogs are actually full-fledged explorers. For example, I fairly often find myself quick-previewing a file to be sure it's the correct one when I select it.


Thankfully on Macos x and Windows, the file picker isn't provided by the UI toolkit (though they usually provide abstract methods of calling it, for convenience).

Linux is now working towards that goal as well, in the form of XDG Desktop Portal. It puts the Desktop Environment (or third party provider) in charge of providing services like the file dialog/picker/chooser for better integration/coherence. It's not been fully adopted yet, but I'm very excited about it because GTK's file chooser is just awful and I want to provide my own to those apps!


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: