[HN Gopher] Everything old is new again: memory optimization
___________________________________________________________________
Everything old is new again: memory optimization
Author : ibobev
Score : 165 points
Date : 2026-03-23 18:22 UTC (4 days ago)
(HTM) web link (nibblestew.blogspot.com)
(TXT) w3m dump (nibblestew.blogspot.com)
| est wrote:
| I think py version can be shortened as:
|
| from collections import Counter
|
| stats = Counter(x.strip() for l in open(sys.argv[1]) for x in l)
| voidUpdate wrote:
| Would that decrease memory usage though?
| est wrote:
| see https://news.ycombinator.com/item?id=47543756
| griffindor wrote:
| Nice!
|
| > Peak memory consumption is 1.3 MB. At this point you might want
| to stop reading and make a guess on how much memory a native code
| version of the same functionality would use.
|
| I wish I knew the input size when attempting to estimate, but I
| suppose part of the challenge is also estimating the runtime's
| startup memory usage too.
|
| > Compute the result into a hash table whose keys are string
| views, not strings
|
| If the file is mmap'd, and the string view points into that,
| presumably decent performance depends on the page cache having
| those strings in RAM. Is that included in the memory usage
| figures?
|
| Nonetheless, it's a nice optimization that the kernel chooses
| which hash table keys to keep hot.
|
| The other perspective on this is that we sought out languages
| like Python/Ruby because the development cost was high, relative
| to the hardware. Hardware is now more expensive, but development
| costs are cheaper too.
|
| The take away: expect more push towards efficiency!
| pjc50 wrote:
| >> Peak memory consumption is 1.3 MB. At this point you might
| want to stop reading and make a guess on how much memory a
| native code version of the same functionality would use.
|
| At this point I'd make two observations:
|
| - how big is the text file? I bet it's a megabyte, isn't it?
| Because the "naive" way to do it is to read the whole thing
| into memory.
|
| - all these numbers are way too small to make meaningful
| distinctions. Come back when you have a gigabyte. It gets more
| interesting when the file doesn't fit into RAM at all.
|
| The state of the art here is : https://nee.lv/2021/02/28/How-I-
| cut-GTA-Online-loading-times... , wherein our hero finds the
| terrible combination of putting the whole file in a single
| string and then running strlen() on it for every character.
| dgb23 wrote:
| > all these numbers are way too small to make meaningful
| distinctions. Come back when you have a gigabyte.
|
| I have to disagree. Bad performance is often a result of a
| death of a thousands cuts. This function might be one among
| countless similarly inefficient library calls, programs and
| so on.
| rcxdude wrote:
| If you're not putting a representative amount of data
| through the test, you have no idea if the resource usage
| you're seeing scales with the amount of data or is just a
| fixed overhead if the runtime.
| kloop wrote:
| > how big is the text file? I bet it's a megabyte, isn't it?
|
| The edit in the article says ~1.5kb
| pjc50 wrote:
| Single page on many systems, which makes using mmap() for
| it even funnier.
| Filligree wrote:
| Not to mention inefficient in memory use. I would have
| expected a mention of interning; using string-views is
| fine, but making it a view of 4kB cache pages is not
| really.
|
| Though I believe the "naive" streaming read could very
| well be superior here.
| veunes wrote:
| I suspect it'll be selective
| zozbot234 wrote:
| > If the file is mmap'd, and the string view points into that,
| presumably decent performance depends on the page cache having
| those strings in RAM.
|
| Not so much, because you only need some fraction of that memory
| when the program is actually running; the OS is free to evict
| it as soon as it needs the RAM for something else. Non-file-
| backed memory can only be evicted by swapping it out and that's
| way more expensive,
| biorach wrote:
| "copyright infringement factories"
| maipen wrote:
| Tells you right away where this is coming from.
| muskstinks wrote:
| The critisism is valid. The problem is how you value this
| critism.
|
| I agree they are stealing it but I also see the benefit of it
| for society and for myself.
|
| Suckerberg downloaded terabytes of books for training, while
| people around me got sued to hell 20 years ago for
| downloading one mp3 file.
| anthk wrote:
| Anna's Archive. Aaron Swartz.
|
| FB and so are CIA fronts and they can do anything they
| please. Until they hit against Disney and lobbying giants
| and if a CIA idiot tries to sue/bribe/blackmail them they
| can order Hollywood to rot their images into pieces with
| all the wars they promoted in Middle East and Latin America
| just to fill the wallets of CEO's. That among some social
| critique movie on FB about getting illegal user data all
| over the world to deny insurances and whatnot. And OFC with
| a clear mention of the Epstein case with related people,
| just in case the Americans forgot about it.
|
| Then the US industry and military complex would collapse in
| months with brainwashed kids running away from the army.
| Not to mention to the Call of Duty franchise and the like.
| It would be the end of Boeing and several more, of course.
| To hell to profit driven wars for nothing.
|
| Ah, yes, AIPAC lobbies and the like. Good luck taming right
| wing wackos hating the MAGA cult more than the 'woke'
| people themselves. These will be the first ones against you
| after sinking the US image for decades, even more than the
| illegal Iraq war with no WMD's and the Bush/Cheney mafia.
|
| The outcome of this? proper and serious engineering a la
| Airbus. Instant profit-driven MBA and war sickos being
| kicked out from the spot. OFC the AI snakeoil sellers
| except for the classical AI/NN against _concrete_ cases
| (image detection and the like), these will survive fine,
| even better because these kind of jobs are highly specific
| and they are not statistical text parrots. They can provide
| _granted_ results unlike LLM 's prone to degrade because
| the human based content feeding needs to be continuous,
| while for tumour detection a big enough sample can cover a
| 99% of the cases.
|
| R&D on electric vehicles/energy and nuclear power like
| nowhere else. And, for sure, the EV equivalent of a Ford T
| for Americans. A cheap and reliable one, good enough for
| the common Joe/Mary without being a luxury item. A new
| Golden Age would rise, for sure. But the oil mafia will try
| to fight them like crazy.
| yieldcrv wrote:
| they got sued for uploading actually
|
| and Zuck isn't sued for downloading either, he is sued for
| reproduction by the AI not being derivative enough, but so
| far all branches of government support that
| Dylan16807 wrote:
| Do you mean something specific, because that sounds like a
| criticism but with some blanks that need to be filled in.
|
| If you just mean they come across as annoyed by AI, that's
| true, but that's also way too wide of a category to infer
| basically anything else about them.
| MrBuddyCasino wrote:
| I don't know how anyone can call the most amazing invention in
| computer science of the last 20 years "copyright infringement
| factories". We went from the ST:NG ship computer being
| futuristic tech to "we kinda have this now". Its like calling
| cars "air pollution factories", as if that was their only
| purpose and use.
|
| A fundamentally anti-civilisational mindset.
| vor_ wrote:
| I'm sorry, but you're acting obtuse if you pretend you don't
| know why they're being called that.
| saintfire wrote:
| The people pushing this technology, that accelerates climate
| change, have lobbied the government to circumvent typical
| roadblocks created by society to limit sensationalist
| development. Incidentally, the same people who talk about how
| dangerous AI will be for society, but don't worry, they're
| going to be the one to deliver it safely.
|
| Now, I don't believe AI will ever amount to enough to be a
| critical threat to human life, you know, beyond the immense
| amounts of wasted energy they propose to convert into
| something more useful, like a market crash or heat and noise,
| or both.
|
| Not sure how you can call someone opposed to any of that
| "anti-civilisational" matter-of-factly.
| muskstinks wrote:
| You can see both sides, critzise how its done and still
| wanting to have the result of it.
|
| Its a little bit hypocritic which often enough ends in
| realism aka "okay we clearly can't fight their copyright
| infridgments because they are too powerful and too rich but
| at least we can use the good side of it".
|
| Nothing btw. enforces all of this to happen THAT fast besides
| capitalism. We could slow down, we could do it better or more
| right.
| ElectronCharge wrote:
| LLMs are amazing technology. It's crazy to interact with
| something that knows a lot about effectively everything
| that's ever been written, as well as mimicking human
| cognition to a large degree.
|
| What LLMs are NOT is intelligent in the same way as a human,
| which is to say they are not "AGI". They may be loosely AGI-
| equivalent for certain tasks, software development being the
| poster child. LLMs have no equivalent of "judgement", and
| they lie ("hallucinate") with impunity if they don't know the
| answer. Even with coding, they'll often do the wrong thing,
| such as writing tests that don't test anything.
|
| It seems likely that LLMs will be one component of a truly
| conscious AI (AGI+), in the same way our subconscious
| facility to form sentences is part of our intelligence. We'll
| see how quickly the other pieces arrive, if ever.
| tzot wrote:
| Well, we can use memoryview for the dict generation avoiding
| creation of string objects until the time for the output:
| import re, operator def count_words(filename):
| with open(filename, 'rb') as fp: data=
| memoryview(fp.read()) word_counts= {} for
| match in re.finditer(br'\S+', data): word=
| data[match.start(): match.end()] try:
| word_counts[word]+= 1 except KeyError:
| word_counts[word]= 1 word_counts=
| sorted(word_counts.items(), key=operator.itemgetter(1),
| reverse=True) for word, count in word_counts:
| print(word.tobytes().decode(), count)
|
| We could also use `mmap.mmap`.
| akx wrote:
| This doesn't do the same thing though, since it's not Unicode
| aware. >>> 'x\u2009 a'.split()
| ['x', 'a'] # incorrect; in bytes mode, `\S` doesn't
| know about unicode whitespace >>>
| list(re.finditer(br'\S+', 'x\u2009 a'.encode()))
| [<re.Match object; span=(0, 4), match=b'x\xe2\x80\x89'>,
| <re.Match object; span=(7, 8), match=b'a'>] # correct,
| in unicode mode >>> list(re.finditer(r'\S+', 'x\u2009
| a')) [<re.Match object; span=(0, 1), match='x'>,
| <re.Match object; span=(5, 6), match='a'>]
| contravariant wrote:
| There's bound to be a way to turn a stream of bytes into a
| stream of unicode code points (at least _I think_ that 's
| what python is doing for strings). Though I'm explicitly not
| volunteering to write the code for it.
| est wrote:
| import mmap, codecs from collections import
| Counter def word_count(filepath):
| freq = Counter() decode =
| codecs.getincrementaldecoder('utf-8')().decode
| with open(filepath, 'rb') as f, mmap.mmap(f.fileno(), 0,
| access=mmap.ACCESS_READ) as mm:
| for chunk in iter(lambda: mm.read(65536), b''):
| freq.update(decode(chunk).split())
| freq.update(decode(b'', final=True).split())
| return freq
| zahlman wrote:
| Sure, but making _one_ string from the file contents is
| surely much better than having a separate string per word
| in the original data.
|
| ... Ah, but I suppose the existing code hasn 't avoided
| that anyway. (It's also creating regex match objects, but
| those get disposed each time through the loop.) I don't
| know that there's really a way around that. Given the file
| is barely a KB, I rather doubt that the illustrated
| techniques are going to move the needle.
|
| In fact, it looks as though the entire data structure
| (whether a dict, Counter etc.) should a relatively small
| part of the total reported memory usage. The rest seems to
| be internal Python stuff.
| est wrote:
| OP's .split_ascii() doesn't handle U+2009 as well.
|
| edit: OP's fully native C++ version using Pystd
| zahlman wrote:
| Hmm? Which code are you looking at?
| contravariant wrote:
| For reasons I never quite understood python has a
| collections.Counter for the purpose of counting things. It's a
| bit cleaner.
| fix4fun wrote:
| Digression: Nowadays when RAM is expensive good old zram is
| gaining popularity ;) Try to check on trends.google.com . Since
| 2025-09 search for it doubled ;)
| canpan wrote:
| String views were a solid addition to C++. Still underutilized.
| It does not matter which language you are using when you make
| thousands of tiny memory allocations during parsing.
| https://en.cppreference.com/w/cpp/string/basic_string_view.h...
| pjc50 wrote:
| C# gained similar benefits with Span<>/ReadOnlySpan<>.
| Essential for any kind of fast parser.
| cdcarter wrote:
| Swift too, in 6.3!
| VorpalWay wrote:
| The issue with retrofitting things to an existing well
| established language is that those new features will likely be
| underutilized. Especially in other existing parts of the
| standard library, since changing those would break backwards
| compatibly. std::optional is another example of this, which is
| not used much in the c++ standard library, but would be much
| more useful if used across the board.
|
| Contrast this with Rust, which had the benefit of being
| developed several decades later. Here Option and str (string
| views) were in the standard library from the beginning, and
| every library and application uses them as fundamental
| vocabulary types. Combined with good support for chaining and
| working with these types (e.g. Option has map() to replace the
| content if it exists and just pass it along if None).
|
| Retrofitting is hard, and I have no doubt there will be new
| ideas that can't really be retrofitted well into Rust in
| another decade or two as well. Hopefully at that point
| something new will come along that learned from the mistakes of
| the past.
| menaerus wrote:
| Retrofitting new patterns or ideas is underutilized only when
| it is not worth the change. string_view example is trivial
| and anyone who cared enough about the extra allocations that
| could have happened already (no copy-elision taking place)
| rolled their own version of string_view or simply used
| char+len pattern. Those folks do not wait for the new
| standard to come along when they can already have the
| solution now.
|
| std::optional example OTOH is also a bad example because it
| is heavily opinionated, and having it baked into the API
| across the standard library would be a really wrong choice to
| do.
| VorpalWay wrote:
| Existing APIs for file IO in STL don't return string views
| into the file buffer of the library (when using buffered
| IO). That is something you _could_ do, as an example.
|
| Optional being opinionated I don't think I agree with. It
| is better to have an optional of something that can't be
| null (such as a reference) than have everything be
| implicitly nullable (such as raw pointers). This means you
| have to care about the nullable case when it can happen,
| and _only_ when it can happen.
|
| There is a caveat for C++ though: optional<T&> is larger in
| memory than a rae pointer. Rust optimises this case to be
| the same size (one pointer) by noting that the zero value
| can never be valid, so it is a "niche" that can be used for
| something else, such as the None variant of the Option.
| Such niche optimisation applies widely across the language,
| to user defined types as well. That would be impossible tp
| retrofit on C++ without at the very least breaking ABI, and
| probably impossible even on a language level. Maybe it
| could be done on a type by type basis with an attribute to
| opt in.
| jandrewrogers wrote:
| Niche optimizations are trivial to automate in modern C++
| if you wish. Many code bases automagically generate them.
|
| The caveat is that niche optimizations are not perfectly
| portable, they can have edge cases. Strict portability is
| likely why the C++ standard makes niche optimization
| optional.
| menaerus wrote:
| I work on a codebase which is heavily influenced by the
| same sentiment you share wrt optional and I can tell you
| it's a nightmare. Has the number of bugs somehow
| magically decreased? No, it did not, as a matter of fact
| the complexity that it introduces, which is to be honest
| coupled along with the monadic programming patterns which
| are normally enforced within such environments, just made
| it more probable to introduce buggy code at no obvious
| advantage but at the great cost - ergonomics, reasoning
| about the code, and performance. So, yeah, I will keep
| the position that it is heavily opinionated and not
| solving any real problem until I see otherwise - the
| evidence in really complex C++ production code. I have
| worked with many traditional C and C++ codebases so that
| is my baseline here. I prefer working with latter.
| groundzeros2015 wrote:
| In C you have char*
| kccqzy wrote:
| And the type system does not tell you if you need to call
| free on this char* when you're done with it.
| pjc50 wrote:
| In C you _only_ have char*.
| tosti wrote:
| wchar exists.
|
| (And the possibility to implement whatever you want, ofc.)
| rcxdude wrote:
| Which isn't very good for substrings due to the null-
| termination requirement.
| gostsamo wrote:
| > how much memory a native code version of the same functionality
| would use.
|
| native to what? how c++ is more native than python?
| VorpalWay wrote:
| Native code usually refers to code which is compiled to machine
| code (for the CPU it will run on) ahead of time, as opposed to
| code running in a byte code VM (possibly with JIT).
|
| I would consider all of C, C++, Zig, Rust, Fortran etc to
| produce native binaries. While things like Cython exist, that
| wasn't what was used here (and for various reasons would likely
| still have more overhead than those I mentioned).
| fluoridation wrote:
| Native to the hardware platform.
| dgb23 wrote:
| Not a C++ programmer and I think the solution is neat.
|
| But it's not necessarily an apples to apples comparison. It's not
| unfair to python because of the runtime overhead. It's unfair
| because it's a different algorithm with fundamentally different
| memory characteristics.
|
| A fairer comparison would be to stream the file in C++ as well
| and maintain internal state for the count. For most people that
| would be the first/naive approach as well when they programmed
| something like this I think. And it would showcase what the
| actual overhead of the python version is.
| VorpalWay wrote:
| > A fairer comparison would be to stream the file in C++ as
| well and maintain internal state for the count.
|
| Wouldn't memory mapping the data in Python be the more fair
| comparison? If the language doesn't support that, then this
| seems to absolutely be a fair comparison.
|
| > For most people that would be the first/naive approach as
| well when they programmed something like this I think.
|
| I disagree, my mind immediately goes to mmap when I have to
| deal with a single file that I have to read in it's entirety. I
| think the non-obvious solution here is rather io-uring (which I
| would expect to be faster if dealing with lots of small files,
| as you can load them async concurrently from the file system).
| dgb23 wrote:
| I'd make the bet that "most people" (who can program) would
| not think of mmap, but either about streaming or would even
| just load the whole thing into memory.
|
| Ask a bunch of coding agents and they will give you these two
| versions, which means it's likely that the LLMs have seen
| these way more often than the mmap version. Both Opus and GPT
| even pushed back when I asked for mmap, both said it would
| "add complexity".
| Filligree wrote:
| It does add complexity, and the optimal solution is
| probably not to use it. Consider what happens if a 4kB page
| has only a single unique word in it--you'd still need to
| load it to memory to read the string, it just isn't
| accounted against your process (maybe).
|
| I would have expected something like this:
|
| - Scan the file serially.
|
| - For each word, find and increment a hash table entry.
|
| - Sort and print.
|
| In theory, technically, this does require slightly more
| memory--but it's a tiny amount more; just a copy of each
| unique word, and if this is natural language then there
| aren't very many. Meanwhile, OOP's approach massively
| pressures the page cache once you get to the "print" step,
| which is going to be the bulk of the runtime.
|
| It's not even a full copy of each unique word, actually,
| because you're trading it off against the size of the
| string pointers. That's... sixteen bytes minimum. A lot of
| words are smaller than that.
| VorpalWay wrote:
| That is a valid solution, but what IO block size should
| you use for the best performance? What if you end up
| reading half a word at the end of a chunk?
|
| Handling that is in my opinion way more complex than
| letting the kernel figure it out via mmap. The kernel
| knows way more than you about the underlying block
| devices, and you can use madvise with MADV_SEQUENTIAL to
| indicate that you will read the whole file sequentially.
| (That might free pages prematurely if you keep references
| into the data rather than copy the first occurance of
| each word though, so perhaps not ideal in this scenario.)
| zahlman wrote:
| > It's unfair because it's a different algorithm with
| fundamentally different memory characteristics. A fairer
| comparison would be to stream the file in C++ as well and
| maintain internal state for the count.
|
| The C++ code is still building a tally by incrementing keys of
| a hash map one at a time, and then dumping (reversed) key/value
| pairs out into a list and sorting. The file is small and the
| Python code is GCing the `line` each time through the outer
| loop. At any rate it seems like a big chunk of the Python
| memory usage is just constant (sort of; stuff also gets lazily
| loaded) overhead of the Python runtime, so.
| amelius wrote:
| > AI sociopaths have purchased all the world's RAM in order to
| run their copyright infringement factories at full blast
|
| The ultimate bittersweet revenge would be to run our algorithms
| inside the RAM owned by these cloud companies. Should be possible
| using free accounts.
| muskstinks wrote:
| I'm always confused as hell how little insight we have in memory
| consumption.
|
| I look at memory profiles of rnomal apps and often think "what is
| burning that memory".
|
| Modern compression works so well, whats happening? Open your
| taskmaster and look through apps and you might ask yourself this.
|
| For example (lets ignore chrome, ms teams and all the other
| bloat) sublime consumes 200mb. I have 4 text files open. What is
| it doing?
|
| Alone for chrome to implement tab suspend took YEARS despite
| everyone being aware of the issue. And addons existed which were
| able to do this.
|
| I bought more ram just for chrome...
| Orygin wrote:
| 200Mb for Sublime does not seem so bad when compared to Postman
| using 4Gb on my machine...
| veunes wrote:
| Part of the problem is that modern apps aren't really "one
| thing" anymore
| senfiaj wrote:
| It's partly because there are layers of abstractions
| (frameworks, libraries / runtimes / VM, etc). Also, today's
| software often has other pressures, like development time,
| maintainability, security, robustness, accessibility,
| portability (OS / CPU architecture), etc. It's partly because
| the complexity / demand has increased.
|
| https://waspdev.com/articles/2025-11-04/some-software-bloat-...
| gwbas1c wrote:
| Basically, the short answer is that most memory managers
| allocate more memory than a process needs, and then reuse it.
|
| IE, in a JVM (Java) or dotnet (C#) process, the garbage
| collector allocates some memory from the operating system and
| keeps reusing it as it finds free memory and the program needs
| it.
|
| These systems are built with the assumption that RAM is cheap
| and CPU cycles aren't, so they are highly optimized CPU-wise,
| but otherwise are RAM inefficient.
| pjc50 wrote:
| https://learn.microsoft.com/en-us/sysinternals/downloads/vmm...
| for an empty sublime text window gives me:
|
| - 100MB 'image' (ie executable code; the executable itself plus
| all the OS libraries loaded.)
|
| - 40MB heap
|
| - 50MB "mapped file", mostly fonts opened with mmap() or the
| windows equivalent
|
| - 45MB stack (each thread gets 2MB)
|
| - 40MB "shareable" (no idea)
|
| - 5MB "unusable" (appears to be address space that's not usable
| because of fragmentation, not actual RAM)
|
| Generally if something's using a lot of RAM, the answer will be
| bitmaps of various sorts: draw buffers, decompressed textures,
| fonts, other graphical assets, and so on. In this case it's
| just allocated but not yet used heap+stacks, plus 100MB for the
| code.
|
| Edit: I may be underestimating the role of binary code size.
| Visual Studio "devenv.exe" is sitting at 2GB of 'image'. Zoom
| is 500MB. VSCode is 300MB. Much of which are app-specific, not
| just Windows DLLs.
| Capricorn2481 wrote:
| But I have sublime text open with a hundred files and it's
| using 12mb.
| pjc50 wrote:
| And how does that breakdown in vmmap? I'm guessing that's
| working set vs. the whole virtual memory allocation (which
| is definitely always an overestimate and not the same as
| RAM)
| wild_egg wrote:
| Virtual memory doesn't matter at all. It's virtual. You
| can take 2TB of address space, use 5MB of it, and nothing
| on the system cares.
| muskstinks wrote:
| Tx for the breakdown. I will play around with it later on my
| windows machine.
|
| But isn't it crazy how we throw out so much memory just
| because of random buffers? It feels wrong to me
| pjc50 wrote:
| As pointed out below, quite a lot of that isn't in RAM -
| see "working set".
|
| There's a common noob complaint about "Linux using all my
| RAM!" where people are confused about the headline
| free/buffers numbers. If there's a reasonable chance data
| could be used again soon it's better to leave it in RAM; if
| the RAM is needed for something else, the current contents
| will get paged out. Having a chunk of RAM be genuinely
| unallocated to anything is doing nothing for you.
| fluoridation wrote:
| Nitpick: What you're describing is the disk cache. If a
| process requests more memory than is free, the OS will
| not page out pages used for the cache, it will simply
| either release them (if they're on the read cache) or
| flush them (if they're on the write cache).
| wat10000 wrote:
| Turning these numbers into "memory consumption" gets
| complicated to the point of being intractable.
|
| The portions that are allocated but not yet used might just
| be page table entries with no backing memory, making them
| free. Except for the memory tracking the page table entries.
| Almost free....
|
| A lot of "image" will be mmapped and clean. Anything you
| don't actually use from that will be similarly freeish.
| Anything that's constantly needed will use memory. Except if
| it's mapped into multiple processes, then it's needed but
| responsibility is spread out. How do you count an app's
| memory usage when there's a big chunk of code that needs to
| sit in RAM as long as any of a dozen processes are running?
| How do you count code that might be used sometime in the next
| few minutes or might not be depending on what the user does?
| gmueckl wrote:
| This assumes that executable code pages can be shared
| between processes. I'm skeptical that this is still a
| notable optimization on modern systems because dynamic
| linking writes to executable memory to perform relocations
| in the loaded code. So this would counteract copy on write.
| And at least with ASLR, the result should be different for
| each process anyway.
| cataphract wrote:
| ld writes to the GOT. The executable segment where .text
| lives is not written to (it's position independent code
| in dynamic libraries).
|
| ASLR is not an obstacle -- the same exact code can be
| mapped into different base addresses in different
| processes, so they can be backed by the same actual
| memory.
| wat10000 wrote:
| Dynamic linking doesn't have to write to code. I'm not
| familiar with other platforms, but on macOS, relocations
| are all in data, and any code that needs a relocation
| will indirect through non-code pages. I assume it's
| similar on other OSes.
|
| This optimization is essential. A typical process maps in
| hundreds of megabytes of code from the OS. There are
| hundreds of processes running at any given time.
| Eyeballing the numbers on an older Mac I have here (a
| newer one would surely be worse) I'd need maybe 50GB of
| RAM just to hold the code of all the running processes if
| the pages couldn't be shared.
| Capricorn2481 wrote:
| > sublime consumes 200mb. I have 4 text files open. What is it
| doing?
|
| Huh? Sublime Text? I have like 100 files open and it uses 12mb.
| Sublime is extremely lean.
|
| Do you have plugins installed?
| muskstinks wrote:
| I do not have plugins installed and i have only a handful of
| files open on macos.
|
| Memroy statistics says 200mb and a peak of 750mb in the past
| (for whatever reason)
| Capricorn2481 wrote:
| Is that in Task Manager, or is that not a reliable place to
| look for these statistics?
|
| Edit: From what I can tell, Sublime is allocated 100mb of
| virtual memory even if it's only using about 10mb in
| practice.
| Twirrim wrote:
| A lot of programs over-allocate on virtual memory, but
| don't actually use it, and the OS is smart enough to just
| pretend like it allocated it. I'm sure there's probably
| some justification for it somewhere, but it's hard not to
| see it as some absurd organically achieved agreement.
| Developers used to ask for more memory than their
| application actually needed and caused all sorts of OOM
| problems for end users. OS developers realised this and
| made the OS lie to the app to tell it it got what it
| asked for, and only give it memory as needed. Now
| developers just can't be bothered to set any realistic
| amount of memory, because what's the point, the OS is
| going to ignore it anyway.
|
| Electron really loves to claim absurd amounts of memory,
| e.g. slack has claimed just over 1TB of virtual memory,
| but is only using just north of 200MB.
| muskstinks wrote:
| Real Memory: 138 MB Virtual Memory: 390 GB <<<<<< wtf? :)
| Shared Memory: 143 MB Private Memory: 34 MB
| inetknght wrote:
| > _I look at memory profiles of rnomal apps and often think
| "what is burning that memory"._
|
| As a corrolary to this: I look at CPU utilization graphs.
| Programs are completely idle. "What is burning all that CPU?!"
|
| I remember using a computer with RAM measured in two-digit
| amounts of MiB. CPU measured in low hundreds of MHz. It felt
| just as fast -- sometimes faster -- as modern computers. Where
| is all of that extra RAM being used?! Where is all of that
| extra performance _going_?! There 's no _need_ for it!
| HPsquared wrote:
| Work expands to fill the available time. This applies to CPU
| time just as it does to project management.
| ThrowawayR2 wrote:
| Next time you see someone on HN blithely post "CPU / RAM is
| cheaper than developer time", it's them. That is the sort of
| coder who are collectively wasting our CPU and RAM.
| anthk wrote:
| Even an editor running under Inferno plus Inferno itself
| would be lighter than the current editors by large. And
| that with the VM being weighted on. And Limbo it's a
| somewhat high level language...
| shepherdjerred wrote:
| If you ran a business, would you rather your devs work on
| feature X that could bring in Y revenue, or spend that same
| time reducing CPU/RAM/storage utilization by Z% and gives
| the benefit of ???
| kardos wrote:
| There is probably some low hanging fruit to be harvested
| in terms of memory optimizations, and it could be a
| selling point for the next while as the memory shortage
| persists
| pjc50 wrote:
| > I remember using a computer with RAM measured in two-digit
| amounts of MiB
|
| Yes, so do I. It was limited to 800x600x16 color mode or
| 320x200x256. A significant amount of memory gets consumed by
| graphical assets, especially in web browsers which tend to
| keep uncompressed copies of images around so they can blit
| them into position.
|
| But a lot is wasted, often by routing things through single
| bottlenecks in the whole system. Antivirus programs. Global
| locks. Syncing to the filesystem at the wrong granularity.
| And so on.
| badsectoracula wrote:
| FWIW a two digit amount of MB is usually at least 16MB
| (though with low hundred _s_ of MHz it was probably at
| least 32MB if not 64MB) and most such systems could easily
| do 1024x768 at 16bit, 24bit or 32bit color. At least my
| mid-90s PC could :-P (24bit color specifically, i had some
| slow Cirrus Logic adapter that stored the framebuffer in
| triplets of R,G,B, probably to save RAM but at the cost of
| performance).
| VorpalWay wrote:
| I too wonder that. And it is true on an OS level as well. The
| only worthwhile change in desktop environments since the
| early 2000s has been search as you type launchers. Other than
| that I would happily use something equivalent to Windows XP
| or (more likely) Linux with KDE 3. It seems everything else
| since then has mostly been bloat and stylistic design
| changes. The latter being a waste of time in my opinion.
|
| Of course, some software other than desktop environments have
| seen important innovation, such as LSPs in IDEs which allows
| avoiding every IDE implementing support for every language.
| And SSDs were truly revolutionary in hardware, in making
| computers feel faster. Modern GPUs can push a lot more
| advanced graphics as well in games. And so on. My point above
| was just about your basic desktop environment. Unless you use
| a tiling window manager (which I tried but never liked)
| nothing much has happened for a very long time. So just leave
| it alone please.
| _dain_ wrote:
| >The only worthwhile change in desktop environments since
| the early 2000s has been search as you type launchers.
|
| Add to that: unicode handling, support for bigger displays,
| mixed-DPI, networking and device discovery is much less of
| a faff, sound mixing is better, power management and sleep
| modes much improved. And some other things I'm forgetting.
| ziml77 wrote:
| There are some people who would exclude all of those an
| enhancements because they don't care about them (yes,
| even Unicode, I've seen some people on here argue against
| supporting anything other than ASCII)
| ben-schaaf wrote:
| Completely agree, it would be very helpful to get even just a
| breakdown of what the ram is being used for. It's unfortunately
| a lot of work to instrument.
|
| > sublime consumes 200mb. I have 4 text files open. What is it
| doing?
|
| To add to what others have said: Depending on the platform a
| good amount will be the system itself, various buffers and
| caches. If you have a folder open in the side bar, Sublime Text
| will track and index all the files in there. There's also no
| limit to undo history that is kept in RAM.
|
| There's also the possibility that that 200MB includes the
| subprocesses, meaning the two python plugin hosts and any
| processes your plugins spawn - which can include heavy LSP
| servers.
| pjmlp wrote:
| It is a matter of tooling.
|
| Visual Studio runs the memory profiler in debug mode right from
| the start, it is the default configuration, you need to disable
| it.
|
| https://learn.microsoft.com/en-us/visualstudio/profiling/mem...
| 90d wrote:
| Speaking about optimization, is Windows just too far gone at this
| point? It is comical the amount of resources it uses at "idle".
| yieldcrv wrote:
| as long as you know what architecture questions to ask, agentic
| coding can help with this next phase of optimization really
| quickly
|
| delaying comp sci differentiation for a few months
|
| I wonder if assembly based solutions will become in vogue
| callamdelaney wrote:
| I shove everything in memory, it's a design decision. Memory is
| still cheap, relatively.
| veunes wrote:
| Not "C++ everywhere again" but maybe "understanding memory again"
| gwbas1c wrote:
| A lot of frameworks that use variants of "mark and sweep" garbage
| collection instead of automatic reference counting are built with
| the assumption that RAM is cheap and CPU cycles aren't, so they
| are highly optimized CPU-wise, but otherwise are RAM inefficient.
|
| I wonder if frameworks like dotnet or JVM will introduce
| reference counting as a way to lower the RAM footprint?
| vaylian wrote:
| Unlikely. Maybe I'm overly optimistic, but I think it's fairly
| likely that the RAM situation will have sorted itself out in a
| few years. Adding reference counting to the JVM and .NET would
| also take considerable time.
|
| It makes more sense for application developers to think about
| the unnecessary complexity that they add to software.
| xyzzy_plugh wrote:
| That's not strictly true. Mark and sweep is tunable in ways ARC
| is not. You can increase frequency, reducing memory at the cost
| of increased compute, for example.
| cogman10 wrote:
| M&S also doesn't necessitate having a moving and compacting
| GC. That's the thing that actually makes the JVM's heap
| greedy.
|
| Go also does M&S and yet uses less memory. Why? Because go
| isn't compacting, it's instead calling malloc and free based
| on the results of each GC. This means that go has slower
| allocation and a bigger risk of memory fragmentation, but
| also it keeps the go memory usage reduced compared to the
| JVM.
| pjc50 wrote:
| Reference counting in multithreaded systems is much more
| expensive than it sounds because of the synchronization
| overhead. I don't see it coming back. I don't think it saves
| massive amounts of memory, either, especially given my
| observation with vmmap upthread that in many cases the code
| itself is a dominant part of the (virtual) memory usage.
| zozbot234 wrote:
| If you use an ownership/lifetime system under the hood you
| only pay that synchronization overhead when ownership truly
| changes, i.e. when a reference is added or removed that might
| actually impact the object's lifecycle. That's a rare case
| with most uses of reference counting; most of the time you're
| creating a "sub"-reference and its lifetime is strictly
| bounded by some existing owning reference.
| cogman10 wrote:
| There are 2 unavoidable atomic updates for RC, the
| allocation and the free event. That alone will
| significantly increase the amount of traffic per thread
| back to main memory.
|
| A lifetime system could possibly eliminate those, but it'd
| be hard to add to the JVM at this point. The JVM sort of
| has it in terms of escape analysis, but that's notoriously
| easy to defeat with pretty typical java code.
| ridiculous_fish wrote:
| Why would an allocation require an atomic write for a
| reference count?
|
| Swift routinely optimizes out reference count traffic.
| cogman10 wrote:
| > Why would an allocation require an atomic write for a
| reference count?
|
| It won't always require it, but it usually will because
| you have to ensure the memory containing the reference
| count is correctly set before handing off a pointer to
| the item. This has to be done almost first thing in the
| construction of the item.
|
| It's not impossible that a smart compiler could see and
| remove that initialization and destruction if it can
| determine that the item never escapes the current scope.
| But if it does escape it by, for example, being added to
| a list or returned from a function, then those two atomic
| writes are required.
| adrian_b wrote:
| Incrementing or decrementing a shared counter is done with an
| atomic instruction, not with a locked critical section.
|
| This has negligible overhead in most cases. For instance, if
| the shared counter is already in some cache memory the
| overhead is smaller than a normal non-atomic access to the
| main memory. The intrinsic overhead of an atomic instruction
| is typically about the same as that of a simple memory access
| to data that is stored in the L3 cache memory, e.g. of the
| order of 10 nanoseconds at most.
|
| Moreover, many memory allocators use separate per-core memory
| heaps, so they avoid any accesses to shared memory that need
| atomic instructions or locking, except in the rare occasions
| when they interact with the operating system.
| usrnm wrote:
| Atomic operations, especially RMW operations are very
| expensive, though. Not as expensive as a syscall, of
| course, but still a lot more expensive than non-atomic
| ones. Exactly because they break things like caches
| cogman10 wrote:
| Not only that, they write back to main memory. There's
| limited bandwidth between the CPU and main memory and
| with multithreading you are looking at pretty
| significantly increasing the amount of data transferred
| between the CPU and memory.
|
| This is such a problem that the JVM gives threads their
| own allocation pools to write to before flushing back to
| the main heap. All to reduce the number of atomic writes
| to the pointer tracking memory in the heap.
| gwbas1c wrote:
| That's why Rust has Rc<> for single-threaded structs, and
| Arc<> for thread-safe structs.
| tombert wrote:
| I've been rewriting a lot of my stuff in Rust to save memory.
|
| Rust is high-level enough to still be fun for me (tokio gives me
| most of the concurrency goodies I like), but the memory usage is
| often like 1/10th or less compared to what I would write in
| Clojure.
|
| Even though I love me some lisp, pretty much all my Clojure
| utilities are in Rust land now.
| bcjdjsndon wrote:
| A few things
|
| - since GC languages became prevalent, and maybe high level
| programming in general, coders arent as economic with their
| designs. Memory isn't something a coder should worry about
| apparently.
|
| - far more people code apps in web languages because they don't
| know anything else. These are anywhere from 5-10 levels of
| abstraction away from the metal, naturally inefficient.
|
| - increasing scope... I can only describe this one by example,
| web browsers must implement all manner of standards etc that it's
| become a mammoth task, especially compared to 90s. Same for
| compilers, oses, heck even computers thenselves were all one-man
| jobs at some point because things were simpler cos we knew less.
| 1vuio0pswjnm7 wrote:
| Been waiting for online commentary about programming to start
| acknowledging this situation as it pertains to writing programs
|
| Memory and storage are not "cheap" anymore. Power may also rise
| in cost
|
| Under these conditions, memory usage and binary size are
| irrefutably relevant^1
|
| To some, this might feel like going backwards in time toward the
| mainframe era. Another current HN item with over 100 points,
| "Hold on to your hardware", reflects on how consumer hardware may
| change as a result
|
| To me, the past was a time of greater software efficiency;
| arguably this was necessitated by cost. Perhaps higher costs in
| the present and future could lead to better software quality. But
| whether today's programmers are up for the challenge is
| debatable. It's like young people in finance whose only
| experience is in a world with "zero" interest rates. It's easier
| to whine about lowering rates than to adapt
|
| With the money and poltical support available to "AI" companies,
| the incentive for efficiency of any kind is lacking. Perhaps
| their "no limits" operations, e.g., its effects on supply, may
| provide an incentive for others' efficiency
|
| 1. As an underpowered computer user that compiles own OS and
| writes own simple programs, I've always rejected large binary
| size and excessive memory use, even in times of "abundance"
| zahlman wrote:
| > This sounds like a job for Python. Indeed, an implementation
| takes fewer than 30 lines of code.
|
| I don't know if the implementation is written in a "low-level"
| way to be more accessible to users of other programming
| languages, but it can certainly be done more simply leveraging
| the standard library: from collections import
| Counter import sys with open(sys.argv[1]) as f:
| words = Counter(word for line in f for word in line.split())
| for word, count in words.most_common(): print(count,
| word)
|
| At the very least, manually creating a (count, word) list from
| the dict items and then sorting and reversing it in-place is
| ignoring common idioms. `sorted` creates a copy already, and it
| can be passed a sort key and an option to sort in reverse order.
| A pure dict version could be: import sys
| with open(sys.argv[1]) as f: counts = {} for line
| in f: for word in line.split():
| counts[word] = counts.get(word, 0) + 1 stats =
| sorted(counts.items(), key=lambda item: item[1], reverse=True)
| for word, count in stats: print(count, word)
|
| (No, of course none of this is going to improve memory
| consumption meaningfully; maybe it's even worse, although
| intuitively I expect it to make very little difference either
| way. But I really feel like if you're going to pay the price for
| Python, you should get this kind of convenience out of it.)
|
| Anyway, none of this is exactly revelatory. I was hoping we'd see
| some deeper investigation of what is actually being allocated.
| (Although I guess really the author's goal is to promote this
| Pystd project. It does look pretty neat.)
| yakkomajuri wrote:
| The abrupt ending was funny and then I realized the author is
| Finnish and it all made sense.
|
| Nice post.
|
| (P.S. I'm also Finnish)
| wbsun wrote:
| It's less about 'old vs. new' and more about the evolving trade-
| offs dictated by the constraints of the era. There have always
| been engineers trying to squeeze every last drop of performance
| out of the bits available to them.
| baud9600 wrote:
| Strange days we live in. Python and C++? What about a line of
| bash:
|
| tr -s '[:space:]' '\n' < file.txt | sort | uniq -c | sort -rn
|
| I'd like to know the memory profile of this. The bottleneck is
| obviously sort which buffers everything in memory. So if we
| replace this with awk using a hash map to keep count of unique
| words, then it's a much smaller data set in memory:
|
| tr -s '[:space:]' '\n' < file.txt | awk '{c[$0]++} END{for(w in
| c) print c[w], w}' | sort -rn
|
| I'm guessing this will beat Python and C++?
| pjscott wrote:
| > I'd like to know the memory profile of this. The bottleneck
| is obviously sort which buffers everything in memory.
|
| That's not obvious to me. I checked the manuals for sort(1) in
| GNU and FreeBSD, and neither of them buffer everything in
| memory by default. Instead they read chunks to an in-memory
| buffer, sort each chunk, and (if there are multiple chunks) use
| the filesystem as temporary storage for an external mergesort.
|
| This sorting program was originally developed with memory-
| starved computers in mind, and the legacy shows.
| knome wrote:
| >which buffers everything in memory
|
| gnu sort can spill to disk. it has a --buffer-size option if
| you want to manually control the RAM buffer size, and a
| --temporary-directory option for instructing it where to spill
| data to disk during sort if need be.
| kristianp wrote:
| How much memory does the C++ compiler use when compiling the
| program? I wonder how that compares to the python program? Not a
| completely unrelated metric.
|
| Would the rust compiler use much more memory compiling a
| comparable program to the C++ version?
___________________________________________________________________
(page generated 2026-03-27 23:00 UTC)