The economics of a web-based book - <AUTHOR> http://practicaltypography.com/economics-year-one.html <comment><USER2> I'd suggest, if you want to run publishing on niche business topics as a business, that you take a page out of the Nathan Barry / Brennan Dunn / et al formula. (There exist many others who do similar things, but they're locally recognizable examples of it.)

Trade the book, or some other incentive [+], for the customer's email address. Attention is fleeting, but relationships with one's email list are enduring, if you do them correctly.

Send people who give you their email address lots of valuable stuff for free. Then, ask them to buy your products. Charge an appropriate amount for those products.

The math is, as has been widely reported by people writing books/etc on HN about a wide range of niche business topics, _quite attractive_. (Ballpark numbers? 650k people with some level of interaction with an online version of the book implies probably between 20k and 50k folks who would have been happy to swap their email address for a downloadable version. That's basically a nice house which temporarily happens to exist inside of an e.g. Mailchimp account.)

[+] I will note the author appears to have aesthetic reasons to reject creating a PDF/e-book/etc version of the book, despite people literally offering him money to do so. [http://practicaltypography.com/why-there's-no-e- book-or-pdf....](http://practicaltypography.com/why-there's-no-e-book-or- pdf.html) There _must be_ something which adds incremental value to the book which would justify incremental closeness to a portion of the readers.

<comment><USER3> First, I want to congratulate Matthew on creating a truly valuable resource that people love and are learning from. That's the hardest part of any online business. Typography for Lawyers and Practical Typography are great resources that deserve every bit of praise and attention they've received.

Whenever someone creates something this valuable I like to see that they get paid for their work. After all, the only thing better than creating products and content people love, is getting paid to do it.

Based on my experience Practical Typography could, without too much effort, make $100,000 per year. So you can think of this comment as a short article titled:

"How Matthew Butterick could have made 27x as much revenue from Practical Typography"

Let's jump in.

// The numbers

Matthew quotes a few numbers in his post, but the two I want to focus on are traffic (649,000 readers, actually I'm not sure if this means visits or visitors...) and book revenue ($3676).

My blog actually averages a similar amount of traffic. For the last two years I've had about 660,000 visits from 420,000 visitors. So about the same amount of traffic. This includes plenty of lower quality viral traffic from communities like Hacker News.

I wrote three books [1] and each one of them made $100,000 within their first year (roughly). That tells me with the same amount of traffic—which is probably better targeted than mine—it's reasonable that Matthew could make $100,000 off of one book.

That's 27x times as much revenue as he actually made ($3676). What I find most interesting is that increasing revenue by 27x actually comes down to three simple changes.

// #1: Have a price

If the first rule of making money selling products is to actually have a product, then the second is to have a price.

Donations are nice, but you won't get nearly the same results as if you actually had a price for the product.

Now having a price doesn't mean you can't offer the book for free to readers. Michael Hartl gives aways his excellent Rails Tutorial [2] book for free, but charges for the PDF version and extra content. Pat Flynn [3] gave away his LEED Exam study guide for free online and charged for the same content packaged up as an ebook.

I can't share Michael's exact revenue numbers, but he makes a healthy salary from a book he shares for free online. Pat Flynn made over $500,000 in a few years of sales from his LEED certification study guide.

They both gave away their content for free, but unlike Practical Typography, add a price to a version of the product.

// #2: Build an email list

649,000 readers don't mean that much if you can't contact them. I've had plenty of posts that get 50,000 visits in a day, but then the week there is no difference in my business. Traffic is fleeting, viral traffic is just a flash in the pan.

You need a way to push new content and products to your audience, so they have to subscribe in some way. That means Facebook, Twitter, RSS, YouTube, Email, etc.

I'll just cut to the chase: email has the highest engagement and drives the most sales. Often seeing 10-15x the value of other channels [4].

To get email subscribers add an opt-in form to each page on your site giving away an incentive related to the content. In your case that could be a free font, a typography cheat-sheet, or additional lessons. It doesn't have to be a hard sell or seem spammy at all. Just say, "if you liked this content you'll also like... enter your email so I can send it to you."

Then give them an option to opt-in to a free follow-up course on Typography. Keep sending them valuable information and you'll build a loyal following that is happy to read your emails each week or month.

// #3: Setup automated sales pitches

That free email course should be dripping out free training every few days or once a week. After demonstrating plenty of value work a sales pitch into one of the emails. Simply describe the additional resources you've created or the benefits of getting the content in a specific format and ask them to purchase.

Then your next email should be educational content again. An email or two later you can work in another soft sell.

Since email courses are timed to when each subscriber joins, you'll have automated sales pitches going out to each of your subscribers on the perfect schedule. Effectively doing a mini product launch to just a few people each day—except that you don't have to do any work other than set it up.

Then later on in the sequence put in links to your fonts and other products. Having an email list is especially powerful when you have multiple products (as you do). Automated selling for the win!

Patio11 has a great course on this [5]. You can learn a ton just from reading the sales page.

Even if you don't want to charge for your product—which I think would be a huge mistake—you can still put in a request for a donation in one of the automated messages.

// Good authors should make money

Authors not making money from their work is a huge problem. It's frustrating to see someone put out such good content and not be able to make a living from it. Especially when the hard part is writing a great book, and it just takes a few techniques to actually make a living from it. This problem bothers me so much I actually wrote an entire book, Authority [6], to try and solve it.

It's also frustrating to see authors not be able to contact their readers, so I built ConvertKit [7] (email marketing for authors) to solve that problem.

Matthew, I'd love to see you get paid for your work. You deserve it. All these ideas can be implemented now and I bet you'll see great results over the next 12 months.

If you ever want to talk, email me at myfirstname[at]convertkit[dot]com

\</comment></comment><comment></comment><comment>--

[1] [http://nathanbarry.com/books/](http://nathanbarry.com/books/) [2] [http://railstutorial.org](http://railstutorial.org) [3] [http://smartpassiveincome.com](http://smartpassiveincome.com) [4] [https://convertkit.com/2013/email-subscriber- worth/](https://convertkit.com/2013/email-subscriber-worth/) [5] [https://training.kalzumeus.com/lifecycle- emails](https://training.kalzumeus.com/lifecycle-emails) [6] [http://nathanbarry.com/authority](http://nathanbarry.com/authority) [7] [http://convertkit.com](http://convertkit.com)

<comment><USER4> I've made $60k in a year with a highly topical 60-page ebook (PDF only) that I wrote in about a week, mainly by promoting to a mailing list with only a few thousand people on it.

As Nathan says, you need to set a price. Plus, tell people why it's worth more for them to buy and read your book than to either not have the information in it, or spend the time researching themselves.

My book is on supporting Retina screens on web browsers[1]. Yes, you can spend a week reading all you can ok the web about it. Or you spend $49 and know all about it in half an hour and spend the other 39 1/2 hours of the week making money with client work.

[1] [http://retinafy.me](http://retinafy.me)

<comment><USER5> So you got about 1,200 sales out of a list of 3,000 subscribers? Would you mind sharing your exact conversion rate (total # of sales/total # of subscribers) that made you $60k in revenue from this book?

</comment></comment></comment><comment><USER6> Dear Author,

I read (parts) of your book, and didn't pay. Why? Because it was an interesting diversion, not something I was actually interested in. If it had been locked behind a paywall, it would not have affected my life in any meaningful way. It was merely, for better or worse, a pleasant diversion, drifting in a sea full of diversions.

I don't imagine that I am in any way alone in this. Looking at your numbers, I'd say that your 2-300 paying people are the true readership and audience of your book; the rest of us were there for a diversion; because it was another blue link in a sea of blue links.

Don't take this as a negative sign. Typography is necessarily a niche field, and at the least, you've introduced over 600,000 new people to typography. It might not help you now, but if even 0.01% of those 600,000 start investing time and money into typography, well, it's more impact you would have had if the content had been hidden behind a paywall. Cold comfort for sure, but in the age of browsers, typography is a dying art. New blood should always be welcome.

Sincerely, Falcolas

<comment><USER7> You are right. Content is devalued due to abundance and us being a product. I'm sure a lot of us think the same way.

I would challenge us: What value do did we get out of it?

A dollar? $.50? $.10? $.01?

What we support grows. What relationship do we want with content? Do we want advertisers to control everything?

$.10 per casual view madly alters the equation.

Sadly, solutions like Flattr aren't gaining any traction.

<comment><USER8> I was going to donate 5$ or 10$, as I found the whole experiment and write up worth it - especially since the content was available for the masses to pique their interests.

I was unfortunately unable to so through Amazon Payments, as they only accept payments from US citizens at this moment. And I do not want/like to use Paypal.

</comment></comment></comment><comment><USER9> "What's most interesting to me, however, is that so many more readers were willing to buy a font license (at $59–299) than to make a donation (at $5–10). Don't get me wrong—I'm utterly grateful. But it's counterintuitive: I never expected that the cheaper option would be so much less popular. Economists, I invite your explanations."

This is not surprising to me at all. There are multiple reasons why the font sale is easier to make.

The whole book is an excellent sales pitch for the fonts. It's hard to believe you didn't design it that way, because you did everything right. You establish credibility as an expert. You give the reader a gift of useful information. You explain at length the value of the product. You even help the reader see herself as someone who's savvy enough to buy the product (copy like: "You are not apathetic" in the Times New Roman page.)

So you have a great sales pitch for the fonts. Whereas the donation suffers in comparison. There's no quid pro quo -- I understand you see the book itself as the value they'd be paying for, but that value is already in the past by the time they're deciding to donate. It changes the psychology.

Also, plenty of people can plunk down a company card to buy a font (or a book). It's a legitimate business expense that's easy to justify. It can be much harder to justify a "donation" to a random author on the internet. For this reason alone, you'd be better off changing "donation" to an e-book sale, even if the e-book is just the same content as the website.

<comment><USER10> I've recommended this book to many many people in the past, but still haven't gotten around to paying anything for it, largely for the reasons you mention. I looked at his options for payment, and they ranged from "buy one of my fonts", which was tempting but expensive, to "buy _Typography for Lawyers_ ", which I wasn't interested in because I'm not a lawyer, to "donate to me", which felt like I was getting nothing in return.

I'd gladly pay $10-15 for a DRM-free ebook version of this book, even if it's the exact same content as the one online, since I can keep it with me on my mobile devices and be sure that even if the site one day goes down I still own and can read the book.

Or hell, since the author apparently has objections to e-book file formats and closed platforms[0], then give me a clean downloadable desktop version of the website as static HTML or something. Or an app version of the book which is just static HTML wrapped in a web view. Anything along those lines would be fine, as long as I can view it offline and keep it past the point when the author stops paying for his server.

[0] [http://practicaltypography.com/why-there's-no-e-book-or- pdf....](http://practicaltypography.com/why-there's-no-e-book-or-pdf.html)

<comment><USER11> Great points, worth keeping in mind by wannabe authors.

</comment></comment></comment><comment><AUTHOR> I thought this may be of interest to HN since the author posts here occasionally and there has been past discussion of his book/typesetting framework [0].

I personally found "Butterick's Practical Typography" very compelling, and I happily paid for it (apparently well above the median selling price!). I'm a little disappointed to read that Mr. Butterick has only made something less than $15k over the course of the first year; while he seems cheerful about it in the post, I can't imagine that such a payout comes close to covering his opportunity cost for creating a high quality book.

Economic structures that support high-quality open-access content seem to be an ongoing challenge with no clear answers.

[0] [https://news.ycombinator.com/item?id=7822057](https://news.ycombinator.com/item?id=7822057)

<comment><USER12> Thanks for posting this. The online version of Practical Typography is a beautiful presented and handy guide, and I was surprised to see it available for free.

<comment><USER13> > My policy for Practical Typography was simple: reading the book > would require no prepayment. ... Call it an honor system. I've never > used the term free to describe the book, because I don't see it that way.

The book is not free, it's just that the seller leaves it up to you when to pay for it. He's experimenting with a payment protocol different than the one that most of us have used for thousands of years.

</comment></comment></comment><comment><USER14> The author's very own essay is a counter-example to the argument that ad blocking starves the web to death.

What ad blocking does is eliminate lowest common denominator [ that denominator being inversely proportional to the amount of money an advertizer is willing to pay ] from pages that are willing to accept the risks of placing crappy advertizing alongside their content.

Good advertizing extends content. The author's pitch for his book and fonts is a case in point. If the essay's page had been covered with advertisements for "local women who want to meet six-pack abs making $5000 a week surfing the internet" or used <blink>, I would not have been pitched the book and fonts because crude forms of advertizing correlate with crude forms of content.

This HN page is another example of big money to be made with a subtle pitch [ the article talks about a business model in the same branch of the spiral galaxy as Ycombinator's ]. The monetization of exhaust fumes is just extremely long tail.

In the end, successful advertizing depends on segmentation. Ad-block serves advertizers needs by not putting ads in front of people who might be inclined to form a negative impression of their business when shown ads on a webpage. I still hold HP creepy based on their targeting me in the early days of this capability [ and before I used ad blocking and noscript ].

</comment><comment><USER15> "Take the example of desktop web browsers. Let's face it, unless you're really slow on the uptake, you've outfitted your web browser with an ad blocker. Ha ha, you win! But wait—that means most web ads are only reaching those who are really slow on the uptake. So their dollars are disproportionately important in supporting the content you're getting ad-free. "Not my problem," you say. Oh really? Since those people are the only ones financially supporting the content, publishers increasingly are shaping their stories to appeal to them. Eventually, the content you liked—well, didn't like it enough to pay for it—will be gone.

Why? Because you starved it to death. The immutable law remains: you can't get something for nothing. The web has been able to defer the consequences of this principle by shifting the costs of content off readers and onto advertisers. But if readers permanently withdraw as economic participants in the writing industry—i.e., refuse to vote with their wallets—then they'll have no reason to protest as the universe of good writing shrinks. (And make no mistake—it's already happening.)"

This. So much this. I'm as guilty as anyone else, but I try to remember to disable my adblocker for sites I like.

<comment><USER16> The problem is that they would shift the content anyway. No reason to appeal to geeks when you can just write empty crap that gets ten times the pageview.

<comment><USER17> Heya. I'm the author of Rebuilding Rails. While I don't (currently) use paid advertising, I _do_ work entirely to appeal to geeks. My second product, a Ruby deployment class, also appeals almost entirely to geeks.

Geeks pay money for things. Not always, but it happens.

That means that the shift of content away from geeks is to some extent in the hands of you (collectively), those same geeks.

And me. I don't just sell to geeks. I'm also a geek who pays money for things aimed at geeks.

You do too, if you think about it.

Many of those things can be advertised. For an example, think about ThinkGeek.

</comment></comment></comment><comment><USER18> As a nitpick (it only scales the numbers a bit) I would guess the proportion of paying _readers_ is actually quite a bit higher than 1:650.

The "650,000 readers" figure quoted here appears to be total hits to the website, which is an extremely optimistic count of the number of readers. It's almost certain that a large proportion of those did not read the book at all, and therefore it's unsurprising they didn't pay for it, either. Raw hit counts include people who just open every link on an HN or Reddit page in a new tab, and may or may not read the resulting tab; it also includes people who clicked on a Google search result and immediately bounced when they realized it wasn't what they were looking for.

It's hard to get good numbers on this sort of thing, but one proxy that's sometimes useful is the number of visitors who clicked on anything beyond the first page they landed on.

</comment><comment><USER19> As one who is just about to publish his 2nd eBook (for money!), none of this resonated with me.

Granted I write for the tech industry; Developers may be more willing to pay for good, specific content that solve day-to-day issues for them.

Newsletters, blogs and book are all great for audience and (personal) brand building. To the point of some naysayers: The time it takes isn't drastic, but it does take some persistence.

"So far, I consider Practical Typography to be a successful experiment in web-based book publishing."

...who needs to experiment? People are making money on eBooks. Period - [https://leanpub.com/bestsellers_lifetime](https://leanpub.com/bestsellers_lifetime).

<comment><USER20> Yeah, I agree. I'm about to publish the second edition of my book targeting a very specific niche of developers[1]. I've made approximately ten times what he quotes there from a small fraction of the traffic his book has gotten.

Of course the real question is, if he had put a paywall on the book, would it have gotten as much traffic? I would say if he had done what Sacha Greif has done with Discover Meteor[2] and made the first few chapters free, he would have done phenomenally better.

[1]: (Shameless plug) [https://www.petekeen.net/mastering-modern- payments](https://www.petekeen.net/mastering-modern-payments)

[2]: [https://www.discovermeteor.com](https://www.discovermeteor.com)

<comment><USER21> Thanks for the plug :) Also relevant, a detailed case study about our sales by the Gumroad folks:

[http://blog.gumroad.com/post/97148570338/discover-meteor- cas...](http://blog.gumroad.com/post/97148570338/discover-meteor-case-study- making-300-000-from-a)

</comment></comment></comment><comment><USER22> It's a nice article that has some good data and makes thoughtful points, but oh my god the readability! That is the most gentle-on-my-eyes reading I think I have ever done on the web.

As someone who has spent a lot of time finding ways to make web pages more readable [https://github.com/chrisbroski/simple- css](https://github.com/chrisbroski/simple-css) I am impressed. After a little revers-engineering to figure out what he did that makes his so much better than mine, I discovered the major difference is he is using the Equity font [http://typographyforlawyers.com/equity.html](http://typographyforlawyers.com/equity.html) (of his own making.) I typically think custom fonts are of dubious value but damn in this instance it made a real difference.

I know the guy wrote a book on typography so I shouldn't be so surprised his typography is so good, but I am so used to being disappointed.

</comment><comment><USER23> I'm writing a book right now with Manning publishing, and I had considered self-publishing as an alternative. In my experience:

\- $2-$3 per book for the author and 2000-3000 copies sounds right. I have heard if your book sells 5000+ copies it is a "very successful" book. As a comparison, my book has sold 1000 copies in the pre-sale so far.

\- I've heard that _on average_ if you publish the traditional route, you can expect to make $10k. If you self-publish, you can expect $50k (no data to back up these figures though)

Overall as an author you do get paid very little. I have thought about publishing my next book on the web like this. The biggest advantage I see is allowing reader comments for every paragraph similar to Medium. Thanks for the writeup! After reading this, I don't think I would put a book online for free. Charging even a little bit seems to be more effective.

</comment><comment><USER24> An anecdotal story to back up the authors point;

I live on a boat, and have done so for 8 years. I know quite a bit about what it takes, what the difficulties are, what to look for when buying a boat, what to do about heating in the winter, etc. etc.

I've thought about writing an e-book about it to give others an insight into what the life is like, how to do it, what it costs, all sorts of practicalities, etc. But I don't think I'll do it after having researched what I can expect to make on it.

It's simply not worth my time because it seems like people on the Internet generally aren't that willing to pay for good content.

<comment><USER21> The problem here is the size of the market, not the fact that people don't want to pay. I assume the market is fairly small (at least, I don't know that many people who live on a boat) so it might not be worth your time for that reason.

But I would be willing to bet that there are people ready to pay anywhere from $20 to $200 for a detailed guide or video course on how to live in a boat.

In fact you could replace "live in a boat" with pretty much any skill you can think of ("play the ukulele", "get better at gardening") and I believe that would still hold true.

</comment></comment><comment><USER25> I think an important thing to consider is that many of the massive number who didn't pay may have read very little and/or been happy not to read the book if there was a charge. That is the number that needs to be compared when comparing the success or failure of free.

</comment><comment><USER26> > My policy for Practical Typography was simple: reading the book > would require no prepayment. ... Call it an honor system. I've never > used the term free to describe the book, because I don't see it that way.

Onarbor, [https://onarbor.com](https://onarbor.com), follows the same ideal.

Digital currencies, with their minuscule fees, pricing flexibility, and instantly international availability, will enable the proper monetization of digital works.

If you don't believe me, believe Chris Dixon: [http://cdixon.org/2013/09/14/the-internet-is-for- snacking/](http://cdixon.org/2013/09/14/the-internet-is-for-snacking/)

</comment><comment><USER27> The point the author made that resonated with me is """Practical Typography has also been a proof of my friend Richard Nash's theory that authors shouldn't think of a book as the end of a relationship with the reader, but as the beginning. The book becomes an introduction to a range of possibilities."""

I have written many books, a few very profitable, but many not. While I really enjoy the writing process (love it!) the huge win has been the relationships formed with my readers.

</comment><comment><USER28> this thread is why i love to read hn. super good stuff here

</comment><comment><USER13> The article says if you use an ad-blocker then you're starving sites and the web to death.

It's not that simple.

I don't mind looking at ads at all. I use an ad blocker because web ads overreach. The egregious ones shout at me, play animations, use my machine cycles, and worst, they track me. I'm creeped out when I look at some page, and then see ads for what was on that page all over subsequent page visits elsewhere.

Ad blockers are not starving the web, they're a feedback mechanism. They're saying "this particular money channel doesn't work." And advertisers and sellers may (I hope) be forced to think of something else.

The web is not only for content producers, it's also for consumers. If you're a producer, and you need to make money, it's your responsibility to figure out how to do that in a way that's acceptable to me, the consumer. And if you can't, well, sorry about that. Do something else. As a ridiculous example, I'd like to sit here on the couch all day and comment on HN, but that's not going to feed me, so I have to get up and go do something totally different for the rest of the day; later I'll bring home some money.

And for now I will continue to use ad blockers, sending the signal that this way of making money isn't going to work with me.

<comment><USER29> I like to picture you, sitting there comfortable on the couch, waxing philosophical, languorously reaching out to pluck the food out of my child's hand as he goes to put it in his mouth. Because it works for you, right, you wanted that sandwich? It was his responsibility to eat the sandwich somewhere out of your reach? Want to talk about egregiousness, let's look at the entitlement on display in this thread.

<comment><USER13> I'm not the one you should be complaining to. You should take it up with the advertisers who fling so much crap at me that I finally threw up my hands and said "Enough!" If you're advertising, then you're running with that crowd, and while I'm sorry if I took a sandwich out of your child's hand, I'm not going to watch rotten teeth, fungal toes (as mentioned elsewhere here) and have myself tracked any more than I personally care to. It's all a continuum.

I don't think there's an RFC that describes how advertisements are supposed to work on the Internet or the Web. These are just something that people have piled on top, and they hope they make money. But there's no right to making money in any particular way.

I'm pretty sure that most of the sites I look at don't have ads on them. Unfortunately, I have to block all ads by default, because the few have ruined it for me. If I visited your site regularly, and your site prompted me to unblock so you could make money, I would do it. Until or unless I found your ads performing some kind of objectionable behavior, and then I would re-block, and maybe not visit again.

I am quite happy to have my visit blocked entirely because of my ad blocking. You should try that, if you need to keep your kid in sandwiches.

I do pay for some content. For example, I pay for New York Times online access. I also block there, and they've never complained to me about it.

I don't feel entitled to anything at all, except to tailor my Internet experience within the bounds of technology and whatever is allowed by site managers.

<comment><USER29> People using ad blockers doesn't actually bother me, I know it's a pointless argument. What bugs me are people sitting around concocting elaborate moral justifications. If you don't like my site, don't visit it. The end.

In any case, fortunately for you (and for everyone else!) content providers by and large have decided that, rather than spend their time and efforts doing whack-a-mole trying to "block" ad blockers from their sites (something you seem to think is much simpler than it is), that it's more in their interest to invest time and resources in developing more of that same content or functionality that drew people there in the first place.

<comment><USER30> If your content disappears, I predict that nobody will care. You're a piece of straw in a haystack.

</comment></comment></comment></comment></comment><comment><USER31> tldr; Author gives away work for free, navel gazes in detail about how many people didn't compensate the author, argues that they should because it is in their best interests. I hope I spared someone the bore. Also included, a rant about adblockers and how bad they are for the web.

My advice: if you want to see book sales, just put a price on your work. And it's not the only way to benefit from a book, financially or otherwise. Spare the world another boring rant how everybody should adapt to make it easier for content creators to get paid. If the ad industry had any kind of self control, people wouldn't have to go through the trouble of installing an adblocker. But we don't live in such world, so why not move on. Not only does an adblocker spare you from having to see ads you'd never engage with any way (and which would otherwise be wasting away precious seconds of your life) you get faster loading pages that are vastly safer. It's in content creators best interests to find ways to fund their work that are also in the best interests of the reader.

The real question is, how to make money providing content. There are many viable answers. Ranting about how people don't behave the desired way is a useless exercise.

<comment><USER30> [http://rocknerd.co.uk/2013/09/13/culture-is-not-about- aesthe...](http://rocknerd.co.uk/2013/09/13/culture-is-not-about-aesthetics- punk-rock-is-now-enforced-by-law/)

The other bit is (a) the presumption that only designated creators are able to create (b) the presumption that the readers care. 'Cos observably they don't. The guy's typesetting is FANTASTIC, but making people care about that _probably_ doesn't involve complaining that the world is wrong.

</comment></comment><comment><USER32> This is an example of Generalising From One Example[1]. The author doesn't have enough information to draw the conclusions that he has drawn. In other words, it's essentially nonsense.

[1] A good essay on the subject - [http://lesswrong.com/lw/dr/generalizing_from_one_example/](http://lesswrong.com/lw/dr/generalizing_from_one_example/)

<comment> kuni-toko-tachi Why the argument can be made that a single example does not prove a point, the author nevertheless provides a description of his experience that can provide others with valuable information. Therefore, to call this nonsense is very unfair. He will learn from his experience, and by taking the time to present in detail what insights he personally gained, a body of knowledge about this subject emerges that will help others.</comment></comment>


GCC 5 in Fedora - <AUTHOR> http://developerblog.redhat.com/2015/02/10/gcc-5-in-fedora/

<comment><USER2> This gives me a strange impression of deja vu ... oh right, RedHat Linux and the not-quite-standard gcc 2.96 version and the solution of having a separate "kgcc" program that you could use to compile the programs that the default gcc broke. I feel slightly old now.

The article does seem to imply that they want to ship the new gcc but keep using the old ABI for a while to keep things compatible. Did I read that right?

<comment><USER3> Both ABIs are shipped in libstdc++, but all other libraries are build with the old ABIs.

You can manually use the libstdc++ with the new ABI, but any of your dependencies that use the problematic ABI must then be rebuild (and will be in F23).

</comment></comment><comment><USER4> Anyone know about LLVM and their plans? The reason I ask is that I'm starting to do some Rust code and one of the nice features is that they use C++ name mangling. I'm guessing I shouldn't assume that there will be interoperation with installed C++ libraries in the short-mid term :-(.

<comment><USER5> LLVM does not need to worry about compatibility here because its libc++ is relatively new and was designed with C++11 requirements in mind, whereas GNU libstdc++ is >20 years old and could not possibly have anticipated these new requirements.

OTOH on Linux you're most likely using clang together with libstdc++, and whether that would use old or new ABI would depend on whether the magic macro is defined in libstdc++ or by g++.

The article says _" the C++ standard library headers installed by the libstdc++-devel RPM will have a different default value for the _GLIBCXX_USE_CXX11_ABI macro"_ so that sounds like clang should do the same thing as g++ here.

</comment></comment><comment><USER6> Is any other distro moving to GCC5 so quickly? (Arch users?)

<comment><USER7> [https://packages.debian.org/experimental/gcc-5](https://packages.debian.org/experimental/gcc-5)

Debian has gcc-5 in experimental. A fact I'm taking advantage of because I want to get Docker working on my sparc server but gccgo is broken on 4.9.

It was fixed a few months ago in the gcc repository but the bleeding edge gcc repo has a broken libsanitizer on sparc. David Miller made some patches but gcc won't accept them(has to go through upstream) and upstream(llvm) have been ignoring them.

[https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59758](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59758)

So I'll have to disable asan or convince llvm to accept davm's patches and then wait for gcc to merge from upstream.

<comment><USER8> I think the parent's comment was about using GCC5 to compile all the distro's packages, not just providing GCC5 for its users. The Fedora article is unclear about what they mean, but the consequences they refer to imply using GCC5 to compile their packages.

As far as I know, while your link shows that Debian is getting ready to provide it to its users, they haven't yet formed plans to use it to compile their packages.

</comment></comment></comment><comment><USER9> Is it stable enough already?

</comment><comment><USER10> So they want to ship a compiler if they can't even build their distribution with it? Seriously?

Had to check the date, we are not yet in April.

<comment><USER11> They're shipping gcc 5, but configures it to use a C++ ABI compatible with older gcc versions, for reasons that does not have anything to do with "they can't even build their distribution with it".</comment></comment>


LAPD should stop stings against Uber - <AUTHOR> http://www.ocregister.com/articles/hundreds-737920-arresting-taxis.html <comment><USER2> It's not just Los Angeles that does this. It might sound a little biased, but a lot of these stings are ridiculous. They're framed as a public safety issue, but the reality is they're a revenue generator.

Dallas, TX does the same thing. If you operate an Uber in the city, you're required to go through the expensive city permit process, and if ou get caught w/o the required sticker and plastic card issued by the city, the fine is like $650. No other cities in the DFW metroplex require such a permit, but Dallas has found a nice little revenue stream. They also require a permit for emergency vehicle operators (private services - city employees are exempt) as well. One that is redundant since the State of Texas, and my employer have already performed the background checks that the City of Dallas claims they're doing. Even if I have my emergency vehicle operator permit (which used to be $17 for 3 years and is now $64/year.. up from $40/yr a year ago) I would still have to go and get one as a "for hire operator" if I wanted to drive for Uber.

They claim it's about safety, but the truth is, it's a cash cow.

</comment><comment><USER3> The author is employed by the Koch-funded "Institute for Justice". (and of course other Koch organizations have worked directly with Uber for lobbying purposes).

</comment><comment><USER4> This seems a bit biased.</comment>


Fail Fail Fail iPad - <AUTHOR> http://gravitationalpull.net/wp/?p=2312 <comment><USER2> There have been so many go-nowhere products from every CES that it's hard to take most of it seriously. In my opinion, the snarkiness that the author decries is warranted and necessary to criticize companies with no strong vision, who release crazy product after crazy product just to throw darts and see what sticks.

Smart laundry? Smart forks? Come on. That still frame of the woman non- chalantly lugging the 27-inch "tablet" between rooms is not only amusing but also embarrassing for the industry.

Not to mention the keynotes which get weirder each year.</comment>


Beyond The Boundary - <AUTHOR> http://www.solipsys.co.uk/new/BeyondTheBoundary.html?HN_20140621 <comment><USER2> Great article Colin. Unfortunately, I read it quickly since I need to get going, but I'll reread it again when I get home.

I did notice a small typo here: "Fil lI nTh eGaps"

<comment><AUTHOR> Good catch! There's a bug in the link generation for the auto-cross- referencing that I have yet to fix - thanks - I'll edit to work around that.

Cheers!</comment></comment>


Facebook and Apple Should Both Be Scared - <AUTHOR> http://chmullig.com/2010/05/facebook-and-apple-should-both-be-scared/

<comment><USER2> When you make such a bold claim as your title, you _need_ to back it up with the first sentence.

This article fails to do so, and is presented in a tiny font with big blocks of text. Because of this, I (and probably 95% of people arriving there) can't be bothered to make the effort to extract the meaning out of this article.

Presentation matters, folks.

<comment><USER3> That's one are where 'old media' tends to get it right -- even the worst of tabloids have better text layout than the average blog.

</comment></comment><comment><USER4> The challenge is that I don't think the general public understand and care as much as the tech community cares.

If as a tech person, you care enough about these issues, what do you do about it? Are you able to steer your friends toward alternatives? How do we make it clear to the non-tech savvy what these issues really are, and how they are affected.

When looking at the app store, most people (I think) feel that they shouldn't care if apple has a closed ecosystem, because that closed ecosystem has 200k+ apps while the more open android and blackberry have less about 1/4 that amount.

With respect to Facebook, even though I understand the implications of Facebook's privacy policies, I'm damned if I can think of a situation where it would really be used against me (maybe I'm just not evil enough). But I know I don't like their policies.

We need more than just 'alternatives', we need alternatives that are better than these in multiple areas.

</comment><comment><USER5> _Facebook and Apple both had near monopolies in their sectors_

Apple never had anything remotely close to a monopoly on mobile phones or even smart phones.

</comment><comment><USER6> I couldn't find the part the explained why they should be scared.

<comment><USER7> It was basically a generic "the more you tighten your grasp, the more worlds that slip through your fingers" mantra. Apple and Facebook don't like change, but if they don't change people will use other products.

Not exactly the content that one would expect from such an attention-grabbing headline that makes such a large claim...</comment></comment>


Python performance: it's not just the interpreter - <AUTHOR> http://blog.kevmod.com/2020/05/python-performance-its-not-just-the-interpreter/ <comment><USER2> The article is a fine example of incremental optimization of some Python, replacing constructs that the standard Python interpreter has overheads in executing with others which trigger fewer of those same overheads.

The title isn't quite right, though. Boxing, method lookup, etc. come under "interpreter" too.

There's a continuum of implementation between a naive interpreter and a full- blown JIT compiler, rather than a binary distinction. All interpreters beyond line-oriented things like shells convert source code into a different format more suitable for execution. When that format is in memory, it can be processed to different degrees to achieve different levels of performance.

For example, if looking up "str" is an issue, an interpreter could cache the last function it got for "str" conditional on some invalidation token (e.g. "global_identifier_change_count"), so it doesn't in fact need to look it up every time.

Boxing can be eliminated by using type-specific representations of the operations, and choosing different code paths depending on checking the actual type. Hoisting those type checks outside of loops is then a big win. Add inlining, and the hoisting logic can see more loops to move things out of. Inlining also gets rid of your argument passing overhead.

None of this requires targeting machine code directly, you can optimize interpreter code, and in fact that's what the back end of retargetable optimizers looks like - intermediate representation is an interpretable format.

Of course things get super-complex super-quickly, but that's the trade-off.

<comment><AUTHOR> I have to disagree that those are "interpreter" overheads, since the later versions of the benchmark do not use the Python interpreter at all yet still suffer from these overheads. Maybe disagreeing on the wording is pedantic though, since I think the real discussion is what can be done about it.

We definitely agree directionally: you can create specific implementations of operations that are fast for their inputs. But look at how specific we have to get here. It's not just on the type of the callable: we have to know the exact callable we are calling (the str type). And its behavior is heavily dependent on its argument type. So we need a code path that is specifically for calling the str() function on ints. I would argue that this is prohibitively specialized for an interpreter, but one can imagine a JIT that is able to produce this kind of specialization, and that's exactly what this blog post is trying to motivate.

<comment><USER3> > So we need a code path that is specifically for calling the str() function > on ints. I would argue that this is prohibitively specialized for an > interpreter,

I would argue it isn't. It's actually less about the ints but about the tuple allocation:

"Now that we've removed enough code we can finally make another big optimization: no longer allocating the args tuple This cuts runtime down to 1.15s." (down from 1.40s)

It seems to me that having special case for one argument, avoiding the tuple allocation in each call is nothing "prohibitively expensive" and would benefit all the functions being called with one argument, and there are enough of such.

Regarding ints vs something else -- somewhere in the code there is anyway different code that does str of int vs str of something else. It's just about accessing that code for that type, it doesn't have to cost having today's speculative execution in the CPU's. The people who produce compiled code know about that and can use it.

<comment><AUTHOR> There's a minor but important point: the amount of work str() has to do depends on its input type. In particular, if arg.__str__() returns a string or a subtype of string or not a string, str() will call different tp_init functions. So we can't get rid of the args tuple until after the optimization of knowing that arg.__str__() returns an string (which is why I did them in that order). So I guess the condition is a little bit weaker than str(int) but it's more than str() of one arg.

<comment><USER4> I think you've proven that the language is a lost cause. It's never going to perform OK.

With all the breakage of Python 3, it's a shame that the causes of slowness were not eliminated. The language could have been changed to allow full ahead- of-time compilation, without all the crazy object look-up. Performance might have been like that of Go.

<comment><USER5> then it will become perl6 which no one uses

<comment><USER6> Perl 6 is mostly not used because it has been renamed to Raku ([https://raku.org](https://raku.org) using the #rakulang tag on social media). Raku is very much being used. If you want to stay up-to-date with Raku developments, you can check out the Rakudo Weekly News [https://rakudoweekly.blog](https://rakudoweekly.blog)

</comment></comment></comment></comment></comment></comment></comment><comment><USER7> I've found it often doesn't matter how fast or slow python is if the bottleneck is outside of python's control.

For example, I wrote an lcov alternative in python called fastcov[0]. In a nutshell, it leverages gcov 9's ability to send json reports to stdout to generate a coverage report in parallel (utilizing all cores)

Recently someone emailed me and advised that if I truly wanted speed, I needed to abandon python for a compiled language. I had to explain, however, that as far as I can tell, the current bottleneck isn't the python interpreter, but GCC's gcov. Python 3's JSON parser is fast enough that fastcov can parse and process a gcov JSON report _before_ gcov can serialize the next JSON.

So really, if I rewrote it in C++ using the most blisteringly fast JSON library I could find, it would just mean the program will spend more time blocking on gcov's output.

In summary: profile your code to see where the bottlenecks are and then fix them. Python is "slow", yes, but often the bottlenecks are outside of python, so it doesn't matter anyway.

[0] [https://github.com/RPGillespie6/fastcov](https://github.com/RPGillespie6/fastcov)

<comment><USER8> Also of course, like any other language, Python is not immune to I/O wait. Recently I was using Python to do some low level audio processing (nothing fancy) and wondering why it was taking so long to process such a small file. Turns out the "wave" stream reader I was using isn't internally buffered, like most of the normal file readers are, so each single-byte read was making the round trip to disk. Simply reading the whole file in one go sped up the program's execution more than 10x.

I like to think I've been doing this long enough to not make such silly mistakes, but when you're focused on a totally different area of the problem, things like this are still quite easy to miss.

<comment><USER9> I'd argue that these things are comparatively easy to address, as one use of `strace` will immediately show bad IO patterns like that, and it works the same way across all programming languages.

</comment></comment></comment><comment><USER10> > _impressively, PyPy [PyPy3 v7.3.1] only takes 0.31s to run the original > version of the benchmark. So not only do they get rid of most of the > overheads, but they are significantly faster at unicode conversion as well._

It is super unfortunate that Pypy struggles for funding every quarter. Funding a mil for Pypy by the top 3 python shops (Google, Dropbox, Instagram?) should be rounding error for these guys...and has the potential to pay off in hundreds of millions atleast (given the overall infrastructure spend).

<comment><USER11> [https://blog.pyston.org/](https://blog.pyston.org/)

You can look at the last release of Pyston for why companies are _not_ funding this work. They're just moving to other languages.

Consider that Dropbox, Google, and Instragram likely spend _much more_ than 1M on optimizing/ improving Python, just to have it still be a relatively slow language.

At some point it becomes way cheaper to just move to other languages that don't require that constant level of effort. Think about how much time and money is spent on things like:

* Adding types to Python

* Improving performance

when you could just move performance critical code to Go/ Rust as Dropbox has done, for a fraction of the cost, with far less maintenance burden.

"Make Python faster" is just a losing game imo. It is fundamentally never going to be as fast as other languages, it's far too dynamic (and that's a huge appeal of the language). Just look at the optimizations done here - moving `str` to local scope to avoid a global lookup? And can you even avoid that? Not without changing semantics - what if I _want_ to change `str` globally?

Still, I was surprised by some of the sorta nutty wins that were achieved here. There's clearly some perf left on the table, and I'm not an expert on interpreters, it just seems _really_ hard to build a semantically equivalent Python ("I can change global functions from a background thread") that can automatically optimize these things.

<comment><USER12> > "Make Python faster" is just a losing game imo. It is fundamentally never > going to be as fast as other languages, it's far too dynamic (and that's a > huge appeal of the language).

This cannot be overstated. Unfortunately python is especially perversely dynamic. After all Javascript and Ruby are highly dynamic, but for a number of reasons have avenues that allow more meaningful optimization gains without changing language semantics. (And although it is true that Google had tremendous resources to pour into V8, it's not like this point doesn't stand - luajit as an example).

Python imho took the performance/productivity trade off way too far. You can get some very effective dynamism with some minimal restrictions that won't so badly shoot yourself in the foot for performance opportunities later. Frankly, for mainstream development, Kotlin or C# gives very pleasant, productive languages with strong ecosystems without paying such a penalty. Swift is good. Go isn't personally my cup of tea, but sure.

Python sort of got a foothold in science.. but that's never been known for being a field of quality software engineering.

<comment><USER13> This is always mentioned as motivation factor, yet Self, Smalltalk, Dylan, Julia, Common Lisp, JavaScript are just as dynamic and manage to have a good set of JITs to choose from.

For example in Smalltalk you can completely change the structure of a given class across the whole process space, thus invalidating everything that the JIT tried to optimize.

So no, Python isn't any special snowflake, rather there isn't the willingness to actually spend the resources improving its JIT story.

<comment><USER14> Is Julia _really_ just as dynamic? Isn't the problem with Python that the low- level C API still needs to be respected during JIT, which just kills performance. (That's why PyPy largely doesn't support it, right?)

Even JS doesn't suffer from this, because folks can't just load V8 extensions in their browser, and Node went through quite a few NAPI versions - forced by V8 changes.

That said, of course it'd be possible to speed up Python/CPython with pouring a lot more money into it. But ... the CPython codebase is already old and has many problems, a lot of backward compatibility gotchas, and relatively few people willing to work on it. Because there's not a big reason to do so. Whereas with JS Google (and thus Mozilla too) was very incentivized to make it fast.

<comment><USER13> Julia, I am not sure how far its dynamism goes, but Dylan, Common Lisp and Smalltalk certainly.

You can even at any point in time stop execution in the debugger, rewrite part of the world and just press continue as if that was how the code was originally written to start with.

<comment><USER12> > You can even at any point in time stop execution in the debugger, rewrite > part of the world and just press continue as if that was how the code was > originally written to start with.

Bit of a straw man, because you wouldn't do this regularly in code. Python on the other hand is a relatively large language at this point, and plain idiomatic python code leans relatively heavier on JIT unfriendly constructs compared to the other languages mentioned. Meanwhile, CL has a whole concept of "compile-time" that doesn't really exist in python. Hence the "perversely" part.

PyPy has used similar tricks as Smalltalk, Self, and JS/V8, many which were old hat in the 90s, but PyPy demonstrates that writing a performant JIT with reasonable memory requirements for real world code is much harder for Python.

<comment><USER13> For me the only thing that PyPy sadly demonstrates is that the Python community doesn't care about JIT support and anyone that wishes to use languages that embrace JITs should look elsewhere instead of having a continuous disappointment.

</comment></comment></comment></comment></comment></comment></comment></comment><comment><USER15> This is a fun benchmark in C++, where you can see that GCC has a more restrictive small string optimization. On my desktop the main python example runs in 3.1s. Then this code

        void example() {
          for (int64_t i = 1; i <= 20; ++i) {
            for (int64_t j = 1; j <= 1'000'000; ++j) {
              std::to_string(j);
            }
          }
        }

runs with GCC in 2.0s and with clang in 133ms, so 15x faster.

I've also benchmarked it in Julia:

        function example()
          for j = 1:20, i = 1:1_000_000
            string(i)
          end
        end

which runs in 592ms. Julia has no small string optimization and does have proper unicode support by default.

None of the compilers can see that the loop can be optimized out.

<comment><USER16> The result of std::to_string is defined to be the same as sprintf, so it depends on the current locale, which is global, mutable state, and initialized to the user's chosen value at runtime. That makes the optimization impossible to do safely without special knowledge about the locale functions.

The awful way that C hacked on locales via implicit global state has been a disaster, leading to both slow and broken code. If you want fast, predictable conversions between numbers and strings, you need to use std::to_chars. Or, std::format for localized conversions.

<comment><USER16> After a little more spelunking, it seems that this particular case was possible to optimize. While you can set the thousands separator for integers in C++ via locales, it only applies to iostreams and not to sprintf. You can also set it for sprintf in POSIX, but it doesn't apply to integers unless you opt-in via the format specifier.

So, there is no applicable locale information needed for integer formatting with std::to_string, and GCC began to replace the naive sprintf-based implementation with one based on std::to_chars in 2017: [https://gcc.gnu.org/legacy-ml/gcc- patches/2017-05/msg02103.h...](https://gcc.gnu.org/legacy-ml/gcc- patches/2017-05/msg02103.html)

The problems I previously mentioned would apply if that were a float, though, and I would still recommend using std::to_chars just so you don't have to think about it.

</comment></comment></comment><comment><USER17> This article seems to be using a very specific definition of interpreter, which is perhaps not what most people think of when they hear "interpreter" ?

If I understand correctly, they call the module generating Python opcodes from Python code the "interpreter", and everything else is a "runtime". But Python opcodes are highly specific to CPython, and they are themselves interpreted, right? Calling the former "interpreter" and the latter something else seems like an artificial distinction.

Not only is this definition of "interpreter" strange, but their definition of "runtime" also seems strange; in other languages, the runtime typically refers to code that assists in very specific operations (for example, garbage collection), not code that executes dynamically generated code.

<comment><USER18> > If I understand correctly, they call the module generating Python opcodes > from Python code the "interpreter",

No, you misunderstand. They explicitly define the interpreter as "ceval.c which evaluates and dispatches Python opcodes". Maybe "evaluate and dispatch" suggest something else to you, but ceval.c really is the code that iterates over a list of opcodes and executes the associated computations. This is absolutely 100% the part of Python that is the interpreter. The module that _generates_ Python opcodes is the "compiler" (or "bytecode compiler"), and the article specifically points out that it's not included.

<comment><USER19> But at what point do function calls from ceval.c stop counting as part of the interpreter? Okay, calling a C function clearly at some point ceases being part of the interpreter, but is the entirety of an attribute lookup (i.e., executing the LOAD_ATTR inst) on an ordinary Python object part of the interpreter or the runtime? In plenty of VMs the object representation is an intrinsic part of the VM design, with the VM having deep knowledge of it.

<comment><USER18> That's a very blurry distinction, and I'm not very interested in it. I was correcting the OP's first two paragraphs, I didn't take a stance on the third.

</comment></comment></comment></comment><comment><USER20> > And impressively, PyPy [PyPy3 v7.3.1] only takes 0.31s to run the original > version of the benchmark. So not only do they get rid of most of the > overheads, but they are significantly faster at unicode conversion as well.

Wow, that's pretty impressive. I never really got to use PyPy though, as it seems that for most programs either performance doesn't really matter (within a couple of orders of magnitude), or numpy/pandas is used, in which case the optimization in calling C outweighs any others.

Can anyone share use cases for PyPy?

<comment><USER21> > Can anyone share use cases for PyPy?

Well, anything that you need to do where the libraries are there and waiting for you.

If you get into the territory of missing libraries it can be a bit of a pain. Otherwise, it's a breath of fresh air as it's almost a drop in replacement.

<comment><USER22> Just to clarify, does it matter if these libraries are pure Python? What I have a hard time understanding is how PyPy is almost a drop-in replacement but then have issues with missing libraries. Couldn't you just pip install the libraries or just literally get the source and run them if they're pure python?

<comment><USER21> > does it matter if these libraries are pure Python

Yes, from what I remember, the issue is where the libraries have compiled C which doesn't work in pypy's JIT compiler.

So long as what you're doing is python code then your code will be ok.

</comment></comment></comment></comment><comment><USER23> I did similar studies about a decode ago for perl with similar results.

But what he's missing are two much more important thing.

1\. smaller datastructures. They are way overblown, both the ops and the data. Compress, trade for smaller data and more simplier ops. In my latest VM I use 32 bit words for each and for each data.

2\. Inlining. A tellsign is when the calling convention (arg copying) is your biggest profiler contributor.

Python's bytecode and optimizer is now much better than perl's, but it's still 2x slower than perl. Python has by far the slowest VM. All the object and method hooks are insane. Still no unboxing or optimize refcounting away, which brought php ahead of the game.

</comment><comment><USER24> When it says that "argument passing" was responsible for 31% of the time, do I understand right that we're talking about this line in the inner loop?

        str(i)

...and the time is spent packing i into a tuple (i,) and then unpacking it again?

are keyword args faster? or they do the same but via dict instead of tuple I guess

<comment><AUTHOR> Yep! It's slightly worse than you would think. Here's a (slightly edited) version of how the argument passing works

      static PyObject *
      unicode_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
      {
          PyObject *x = NULL;
          static char *kwlist[] = {"object", "encoding", "errors", 0};
          char *encoding = NULL;
          char *errors = NULL;

          PyArg_ParseTupleAndKeywords(args, kwds, "|Oss:str",
                                      kwlist, &x, &encoding, &errors))
      }

Notice the call to PyArg_ParseTupleAndKeywords -- it takes an arguments tuple and a format string and _executes a mini interpreter to parse the arguments from the tuple_. It has to be ready to receive arguments as any combination of keywords and positional, but for a given callsite the matching will generally be static.

<comment><USER24> And is that literally every Python function doing that under the hood? Even a 1-arity function?

I don't get what the format string is for.

Anyway, thanks for answering!

<comment><AUTHOR> It used to be this way, but at some point they added a faster calling convention and moved a number of things to it. Calling type objects still falls back to the old convention though.

</comment></comment></comment></comment><comment><USER25> I wanted to play with variations of the code. For that it is useful to make it output a "summary" so you know the variation you tried is computationally equivalent.

For the first benchmark, I added a combined string length calcuclation:

        def main():
         r = 0
         for j in range(20):
           for i in range(1000000):
             r += len(str(i))
         print(r)
        main()

When I execute it:

        time python3 test.py

I get 8.3s execution time.

The PHP equivalent:

        <?php
        function main() {
         $r = 0;
         for ($j=0;$j<20;$j++)
           for ($i=0;$i<1000000;$i++)
             $r += strlen($i);
         print("$r\n");
        }
        main();

When I execute it:

        time php test.php

Finishes in 1.4s here. So about 6x faster.

Executing the Python version via PyPy:

        time pypy test.py

Gives me 0.49s. Wow!

For better control, I did all runs inside a Docker container. Outside the container, all runs are about 20% faster. Which I also find interesting.

Would like to see how the code performs in some more languages like Javascript, Ruby and Java.

<comment><USER26> I've added rust using this code

        fn main() {
            let mut r = 0;

            for _x in 0..20 {
                for y in 0..1_000_000 {
                    r += y.to_string().len();
                }
            }

            println!("{}", r);
        }

Surprisingly PyPy is the fastest

        % hyperfine target/release/perftest "php perftest.php" "python perftest.py" "pypy perftest.py" -w 3
        Benchmark #1: target/release/perftest
        Time (mean ± σ):     624.8 ms ±   9.8 ms    [User: 623.0 ms, System: 0.8 ms]
        Range (min … max):   614.5 ms … 644.0 ms    10 runs

        Benchmark #2: php perftest.php
        Time (mean ± σ):     697.8 ms ±  18.3 ms    [User: 696.7 ms, System: 1.1 ms]
        Range (min … max):   650.1 ms … 718.0 ms    10 runs

        Benchmark #3: python perftest.py
        Time (mean ± σ):      3.326 s ±  0.071 s    [User: 3.313 s, System: 0.003 s]
        Range (min … max):    3.232 s …  3.419 s    10 runs

        Benchmark #4: pypy perftest.py
        Time (mean ± σ):     270.7 ms ±   5.7 ms    [User: 257.5 ms, System: 13.0 ms]
        Range (min … max):   257.8 ms … 277.8 ms    11 runs

        Summary
        'pypy perftest.py' ran
            2.31 ± 0.06 times faster than 'target/release/perftest'
            2.58 ± 0.09 times faster than 'php perftest.php'
            12.29 ± 0.37 times faster than 'python perftest.py'

<comment><USER27> My Java timing is very close to Rust. I have a feeling PyPy is eliminating the string conversion as dead code. When I alter my Java toy code to not prevent elimination of the toString() call it runs close to speed of PyPy.

<comment><USER28> I found Java's to be quicker than Rust:

Java:

        time java Test
        117777800
                0.60 real         0.52 user         0.12 sys

Rust:

        time ./test
        117777800
                1.94 real         1.89 user         0.00 sys

<comment><USER29> What flags did you use with Rust?

<comment><USER28> Both `-O` and `-C opt-level=3`. I'm guessing this is one of those high throughput scenarios where having a GC is quicker because it's able to defer deallocation until the end of the program.

<comment><USER29> -O is opt level 2, incidentally.

Yeah, I think you're right. Very interesting!

</comment></comment></comment></comment></comment></comment></comment><comment><USER30> I think he is making the distinction between 3 different categories that readers are in general lumping into the 'interpreter':

1) Python is executed by an interpreter (necessary overhead)

2) Python as a language is so dynamic/flexible/erfonomic that it has to do things that have overhead (necessary complexity unless you change the language)

3) the specific implementation of the interpreter achieves 1 and 2 in ways that can be significantly slower than necessary

Seems he is pointing out that a lot of performance issues that are generally thought to be due to 1 and 2 are really 3

<comment><USER19> > 2) Python as a language is so dynamic/flexible/erfonomic that it has to do > things that have overhead (necessary complexity unless you change the > language)

As PyPy demonstrates, much of this doesn't need to have anywhere near the overhead that it does in CPython. You can absolutely do better than CPython without changing the language, and you can do better than CPython without a JIT if you start specialising code.

</comment></comment><comment><USER31> Static subset of <language>

While many here correctly observe that the "too much dynamism" can become a performance bottleneck, one has to analyze the common subset of python that most people use and see how much dynamism is intrinsic to those use cases.

Other languages like JS have tried a static subset (Static typescript is a good example), that can be compiled to other languages - usually C.

Python has had RPython, but no one uses it outside of the compiler community.

The argument here is that python doesn't have to be one language. It could be 2-3 with similar syntax catering to different use cases and having different linters.

* A highly dynamic language that caters to "do this task in 10 mins of coding" use case. This could be used by data scientists and other data exploration use cases.

* A static subset where performance is at a premium. Typically compiled down to another language. Strict typing is necessary. Performance sensitive and a large code base that lives for many years.

* Some combination of the two (say for a template engine type use case).

A problem with the static use case is that the typing system in python is incomplete. It doesn't have pattern matching and other tools needed to support algebraic data types. Newer languages such as swift, rust and kotlin are more competitive in this space.

</comment><comment><USER32> Kevin knows much more than I do about optimising Python, but aren't lots of the things listed as 'not interpreter overhead' only slow because they're being interpreted? For example you only need integer boxing in this loop because it's running in an interpreter. If it was compiled that would go away. So shouldn't we blame most of these things on 'being interpreted'?

<comment><USER33> Not really. Integer boxing is totally orthogonal to compilation vs interpretation. Assuming we need to preserve program semantics, a naive compiler would have exactly the same boxing overhead. And on the other hand a smart interpreter could eliminate it. (E.g stack allocate integers by default, dynamically detect conditions that require promoting them to the heap, annotate the str builtin as escape safe.)

<comment><USER32> Isn't removing overhead like boxing the main point of a compiler? Seems like if you write a compiler that doesn't do that you might as well not have bothered.

</comment></comment></comment><comment> mark-r > The benchmark is converting numbers to strings, which in Python is > remarkably expensive for reasons we'll get into.

I was a bit disappointed that converting numbers to strings was the only thing he didn't actually discuss. I've discovered that the conversion function is unnecessarily slow, basically O(n^2) on the number of digits. This despite being based on an algorithm from Knuth.

</comment><comment><USER34> I'll never understand why there are so many fast, alternative python interpreters.

Is the language correctness of the official interpreter causing lower performance? Does it prevent using some modules? What's at stake here?

I'm planning to use python as a game scripting language but I hear so much about performance issues that it scares me to try learning how to use it in a project. I love python though.

<comment><USER35> They are mostly JITs or have specific purposes (like stackless python).

JITs like pypy have a raw performance advantage. But implementation is more complex, and there is startup time overhead. Also I have heard CPython reference implementation prefers clarity of code over optimizations.

As for game scripting, Lua is most used one.

</comment></comment><comment><USER36> > In this post I hope to show that while the interpreter adds overhead, it is > not the dominant factor for even a small microbenchmark. Instead we'll see > that dynamic features -- particularly, features inside the runtime -- are to > blame.

I'm probably uneducated here, but I don't understand the distinction between the runtime and the interpreter for an interpreted language? Isn't the interpreter the same as the runtime? What are the distinct responsibilities of the interpreter and the runtime? Is the interpreter just the C program that runs the loop while the runtime is the libpython stuff (or whatever it's called)?

<comment><USER37> Consider a program written in Python. Imagine it converted into some "internal representation" that will be used by the interpreter to execute it.

We can ask questions like "when I write a statement in Python that adds two integers (typically a single machine instruction in a C program), how many machine instructions are executed while running that statement?"

Those are questions about the interpreter - essentially, the raw speed and execution patterns of the language.

But when you're running a Python program (or one written in many other languages these days), there is a lot going on inside the process that you cannot map back to the Python statements you wrote. The most obvious is memory management, but there are several others.

This stuff takes time to execute and can change the behavior of the statements that you did write in subtle (and not so subtle ways).

Note that this kind of issue exists even for compiled languages too: when you run a program written in C++, the compiler will have inserted varying amounts of code to manage a variety of things into the executable that do not map back explicitly to the lines you wrote. This is the "C++ runtime" at work, and even though the "C++ language" may run at essentially machine speed, the runtime still adds overhead in some places.

Interpreted languages are the same, just worse (by various metrics)

<comment><USER36> I understand the notion of a "runtime" in the compiled language case; my question is about the distinction between an interpreter and a runtime in the interpreted language case. Perhaps out of naivety, I would think that the interpreter _is_ the runtime--the interpreter manages the call stack, memory, etc.

<comment><USER37> Well, imagine for example that you wrote this in python:

        s = "hello" + "world"

You could measure the execution of the internal representation of this, and that would be all about the speed of the interpreter.

At some point, something may have to clean up temporaries and that may happen asynchronously with respect to the actual statement execution. Yes, it happens in the same process as the interpreter's execution of the code, but it's a theoretically "optional" cost that has no specific relationship with the statement.

There are interpreters, for example, where you can disable garbage collection. If the overall execution time decreased, it's not because the interpreter got faster, it's just doing less of the "runtime" work.

<comment><USER36> So is there anything besides the GC which satisfies this definition of "runtime"? What other asynchronous activities are going on apart from those of the Python program itself?

</comment></comment></comment></comment></comment><comment><USER38> Replacing str(i) with the f-string f'{i}' lets it run about 2x faster.

</comment><comment><USER39> The article is quite good but the Node.js example is wrong. OP is measuring dead code elimination and general overhead time.

No strings get harmed in the process, run Node.js with --trace-opt to see what's happening.

<comment><AUTHOR> I don't know how to interpret the results of --trace-opt, but when I increase the iteration bounds 10x the running time increases 10x, so I don't think the code is being eliminated. This is with node v12.13.0

</comment></comment><comment><USER40> A potential issue with benchmarks like this is that there are instances where the initial findings don't scale.

I would be interested to see how it does over an operation that takes 1 minute, 5 minutes, 10 minutes.

</comment><comment><USER41> Since people are talking about speed and JIT here, it's worth mentioning Numba ([http://numba.pydata.org](http://numba.pydata.org)). Being in the quant field myself, it's often been a lifesaver - you can implement a c-like algorithm in a notebook in a matter of seconds, parallelise it if needed and get full numpy api for free. Often times if you're doing something very specific, you can beat pandas/numpy versions of the same thing by an order of magnitude.

</comment><comment><USER27> for reference, a pure java version through JMH that takes 0.38 seconds on my machine. This uses parallel stream so its multithreaded.

Single threaded it takes 0.71 seconds. Removing the blackhole to allow dead code elimination takes single thread down to 0.41 seconds. This is close to PyPy, which I assume is dead code eliminating the string conversion as well.

        package org.example;

        import org.eclipse.collections.api.block.procedure.primitive.IntProcedure;
        import org.eclipse.collections.impl.list.Interval;
        import org.openjdk.jmh.annotations.Benchmark;
        import org.openjdk.jmh.infra.Blackhole;

        public class MyBenchmark {

          @Benchmark
          public void testMethod(final Blackhole blackhole) {
            Interval.oneTo(20)
                .parallelStream().forEach((IntProcedure)
                    outer ->
                        Interval.oneTo(1000000)
                            .forEach(
                                (IntProcedure)
                                    inner ->
                                        // prevent dead code elimination
                                        blackhole.consume(Integer.toString(inner))));
          }
        }

<comment><USER21> Not too surprising as dynamic languages take 4-20 times longer at numerical work, from rough experience.

Java/c/c#/c++/rust etc are roughly at the same end of the spectrum (unless you're creating stupid numbers of objects).

Perl/python/ruby, they're dynamic, so expect slower results.

I like the threaded approach you're using.

<comment><USER27> > like the threaded approach you're using.

thanks :) Eclipse Collections can also do batched loops which might speed this up. Telling Java how many threads you want will probably help as well

Interestingly, I tried plain old for(i) loops and the result was exactly the same. At least for Eclipse Collections, the syntactic sugar for ranges and forEach is completely optimized away, apparently.

<comment><USER42> You might already know this, but there is one potential caveat with APIs like that when it comes to performance or at least measuring their performance. The hot loop (the code that actually iterates over your data points) does not live in your code base. But performance often depends on what the JIT compiler makes out of that loop. If the API is used at several locations in your program, the compiler might not be able to generate code that is optimal for your call-site and inputs. Instead, it will generate generalized code that works for all inputs but might be slower.

However, when writing benchmarks, there is often no other code around to force the JIT compiler to generate such generalized code.

The following code demonstrates this. If the parameter _warmup_ is set, I invoke the _forEach_ methods with different inputs first (they do the same but are different methods in the Java byte code). The purpose is to force the compiler to generate generalized code:

        @Param({"true", "false"})
        public boolean warmup;

        @Setup
        public void setup() {
            if (!warmup) {
                Interval.oneTo(20).forEach(
                        (IntProcedure) i -> Interval.oneTo(1_000_000).forEach(
                                (IntProcedure) j -> Integer.toString(j)));
                Interval.oneTo(20).forEach(
                        (IntProcedure) i -> Interval.oneTo(1_000_000).forEach(
                                (IntProcedure) j -> Integer.toString(j)));
                Interval.oneTo(20).forEach(
                        (IntProcedure) i -> Interval.oneTo(1_000_000).forEach(
                                (IntProcedure) j -> Integer.toString(j)));
            }
        }

        @Benchmark
        public void coollectionsBlackhole(Blackhole blackhole) {
            Interval.oneTo(20).forEach(
                    (IntProcedure) i -> Interval.oneTo(1_000_000).forEach(
                            (IntProcedure)j -> blackhole.consume(Integer.toString(j))));
        }

        @Benchmark
        public void collectionsDeadCode() {
            Interval.oneTo(20).forEach(
                    (IntProcedure) i -> Interval.oneTo(1_000_000).forEach(
                            (IntProcedure)j -> Integer.toString(j)));
        }

And the for loop implementations for reference:

        @Benchmark
        public void loopBlackhole(Blackhole blackHole) {
            for (int j = 0; j < 20; j++) {
                for (int i = 0; i < 1_000_000; i++) {
                    blackHole.consume(Integer.toString(i));
                }
            }
        }

        @Benchmark
        public void loopDeadCode() {
            for (int j = 0; j < 20; j++) {
                for (int i = 0; i < 1_000_000; i++) {
                    Integer.toString(i);
                }
            }
        }

        @Benchmark
        public void loopBlackholeOnly(Blackhole hole) {
            for (int j = 0; j < 20; j++) {
                for (int i = 0; i < 1_000_000; i++) {
                    hole.consume(0xCAFEBABE);
                }
            }
        }

On my desktop machine, this gives me the following results (Java 11/Hotspot/C2):

      Benchmark              (warmup)  Mode  Cnt    Score   Error  Units
      collectionsDeadCode        true  avgt   99  162.482 ± 1.615  ms/op
      collectionsDeadCode       false  avgt   99  218.816 ± 4.217  ms/op
      coollectionsBlackhole      true  avgt   99  235.122 ± 1.362  ms/op
      coollectionsBlackhole     false  avgt   99  270.192 ± 1.627  ms/op
      loopBlackhole              true  avgt   99  207.214 ± 1.162  ms/op
      loopBlackhole             false  avgt   99  206.711 ± 0.932  ms/op
      loopBlackholeOnly          true  avgt   99   74.774 ± 0.180  ms/op
      loopBlackholeOnly         false  avgt   99   74.359 ± 0.180  ms/op
      loopDeadCode               true  avgt   99  143.394 ± 0.900  ms/op
      loopDeadCode              false  avgt   99  142.654 ± 0.795  ms/op

All results are for single threaded code. Now, the difference is not huge, but significant. Overall it looks like the invocation of toString(int) is not removed and accounts for most of the runtime.

Just to be clear: I am not saying one should stay way from the stream APIs. As soon as the work per item is more than just a few arithmetic operations, there is a good chance the difference in runtime is negligible. But when doing numerical work (aggregations etc.), a simple loop might be the better option.

Finally, these differences are of course compiler-dependent. For example, C2 might behave differently than Graal and who knows what the future brings.

<comment><USER43> > For example, C2 might behave differently than Graal and who knows what the > future brings.

Yes, the GraalVM compiler is more aggressive on some optimizations that can be especially helpful with streams. Here are numbers from my machine (using ops/s, so _higher_ is better!):

C2 (on Java(TM) SE Runtime Environment (build 1.8.0_251-b08)):

        Benchmark                          (warmup)   Mode  Cnt   Score   Error  Units
        MyBenchmark.collectionsDeadCode        true  thrpt   10   2.179 ± 0.070  ops/s
        MyBenchmark.collectionsDeadCode       false  thrpt   10   1.640 ± 0.055  ops/s
        MyBenchmark.coollectionsBlackhole      true  thrpt   10   1.661 ± 0.109  ops/s
        MyBenchmark.coollectionsBlackhole     false  thrpt   10   1.571 ± 0.062  ops/s
        MyBenchmark.loopBlackhole              true  thrpt   10   1.829 ± 0.077  ops/s
        MyBenchmark.loopBlackhole             false  thrpt   10   1.855 ± 0.062  ops/s
        MyBenchmark.loopBlackholeOnly          true  thrpt   10  19.775 ± 0.184  ops/s
        MyBenchmark.loopBlackholeOnly         false  thrpt   10  20.213 ± 0.491  ops/s
        MyBenchmark.loopDeadCode               true  thrpt   10   2.022 ± 0.057  ops/s
        MyBenchmark.loopDeadCode              false  thrpt   10   1.990 ± 0.074  ops/s

GraalVM Community Edition (current master, close to yesterday's 20.1 release, JDK 8):

        Benchmark                          (warmup)   Mode  Cnt   Score   Error  Units
        MyBenchmark.collectionsDeadCode        true  thrpt   10   3.151 ± 0.118  ops/s
        MyBenchmark.collectionsDeadCode       false  thrpt   10   2.790 ± 0.053  ops/s
        MyBenchmark.coollectionsBlackhole      true  thrpt   10   2.713 ± 0.137  ops/s
        MyBenchmark.coollectionsBlackhole     false  thrpt   10   2.488 ± 0.031  ops/s
        MyBenchmark.loopBlackhole              true  thrpt   10   2.862 ± 0.073  ops/s
        MyBenchmark.loopBlackhole             false  thrpt   10   2.835 ± 0.043  ops/s
        MyBenchmark.loopBlackholeOnly          true  thrpt   10  24.396 ± 1.278  ops/s
        MyBenchmark.loopBlackholeOnly         false  thrpt   10  24.103 ± 0.957  ops/s
        MyBenchmark.loopDeadCode               true  thrpt   10   3.235 ± 0.097  ops/s
        MyBenchmark.loopDeadCode              false  thrpt   10   3.205 ± 0.168  ops/s

GraalVM Enterprise Edition (current master, close to 20.1, JDK 8):

        Benchmark                          (warmup)   Mode  Cnt   Score   Error  Units
        MyBenchmark.collectionsDeadCode        true  thrpt   10   3.748 ± 0.046  ops/s
        MyBenchmark.collectionsDeadCode       false  thrpt   10   2.879 ± 0.042  ops/s
        MyBenchmark.coollectionsBlackhole      true  thrpt   10   2.792 ± 0.059  ops/s
        MyBenchmark.coollectionsBlackhole     false  thrpt   10   2.479 ± 0.040  ops/s
        MyBenchmark.loopBlackhole              true  thrpt   10   3.070 ± 0.046  ops/s
        MyBenchmark.loopBlackhole             false  thrpt   10   3.077 ± 0.057  ops/s
        MyBenchmark.loopBlackholeOnly          true  thrpt   10  25.200 ± 0.760  ops/s
        MyBenchmark.loopBlackholeOnly         false  thrpt   10  25.752 ± 0.481  ops/s
        MyBenchmark.loopDeadCode               true  thrpt   10   3.812 ± 0.161  ops/s
        MyBenchmark.loopDeadCode              false  thrpt   10   3.909 ± 0.109  ops/s

Amusingly, on the OP's benchmark GraalVM also does better than C2, and it somehow (I haven't tried to analyze this yet) makes the serial stream version as fast as the parallel one:

C2:

        Benchmark                            Mode  Cnt  Score   Error  Units
        MyBenchmark.originalParallelStream  thrpt   10  2.547 ± 0.336  ops/s
        MyBenchmark.originalSerialStream    thrpt   10  1.719 ± 0.041  ops/s

GraalVM Community Edition:

        Benchmark                            Mode  Cnt  Score   Error  Units
        MyBenchmark.originalParallelStream  thrpt   10  2.809 ± 0.141  ops/s
        MyBenchmark.originalSerialStream    thrpt   10  2.868 ± 0.090  ops/s

(I work in the GraalVM compiler team at Oracle Labs, I don't speak for Oracle, I'm not claiming anything about whether this is a good or bad benchmark, etc.)

<comment><USER42> The results for the parallel code are indeed interesting. However, the speedup < 2 with C2 is suspicious. Even on an old dual core / 4 thread system (common worker pool should default to 3 workers), I would expect it to be above 2 for this kind of workload.

The last time I looked into Graal (over a year ago), I ran into something I could not make sense of.

We were designing an API with methods similar to Arrays.setAll(double[], IntToDoubleFunction) which we expected to be called with large input arrays from many places in the code base.

The performance of the code generated by C2 dropped as one would expect once you were using more that two functions (lambda functions were no longer inlined and invoked for each element). For simple arithmetic operations the runtime increased by around 5x.

The performance of the code generated by Graal was competitive with C2's code for mono- and bimorphic scenarios for a much larger number of input functions. I don't recall the exact number, but I believe it was around 25. However, once we hit that threshold, performance tanked. I believe the difference was closer to 15x than than C2's 5x.

We never figured out what caused this.

Do you have an idea? Or, since this is probably outdated data, do you know what the expected behavior is nowadays? Is there maybe any documentation on this?

<comment><USER43> _> However, the speedup < 2 with C2 is suspicious. Even on an old dual core / 4 thread system (common worker pool should default to 3 workers), I would expect it to be above 2 for this kind of workload._

The speedup I found on C2 is 2.547 / 1.719 ~= 1.5, the OP's speedup (also on C2) was 0.71 s / 0.38 s ~= 1.9. Not the same factor, but not above 2 either. The benchmark is quite small, so the overhead of setting up the parallel computation might matter. The JDK version might matter as well.

 _> Graal was competitive with C2's code for mono- and bimorphic scenarios for a much larger number of input functions. I don't recall the exact number, but I believe it was around 25._

If we are talking about a call like Arrays.setAll(array, function) and you have one or two candidates for function, then you are mono- or bimorphic. If you have 25 candidates for function, you are highly polymorphic. I don't understand what you mean by a mono- or bimorphic scenario with up to 25 candidates.

Performance will usually decrease as you transition from a call site with few candidates to one with many candidates. I agree that a factor of 15x looks steep, but it's hard to say more without context. The inliners (Community and Enterprise use different ones) are constantly being tweaked, though that's not my area of expertise. It's very possible that this behaves differently than it did more than a year ago.

If you have a concrete JMH benchmark you can link to and are willing to post to the GraalVM Slack (see signup link from [https://www.graalvm.org/community/](https://www.graalvm.org/community/)), that would be great. Alternatively, you could drop me a line at <first . last at oracle dot com>, and I could have a look at a benchmark, without promising anything.

<comment><USER42> > If we are talking about a call like Arrays.setAll(array, function) and you > have one or two candidates for function, then you are mono- or bimorphic. If > you have 25 candidates for function, you are highly polymorphic. I don't > understand what you mean by a mono- or bimorphic scenario with up to 25 > candidates.

Sorry, I phrased that poorly. What I was trying to say was that the moment we introduced a 3rd candidate the performance with C2 decreased by ~5x. Whereas with Graal the performance stayed virtually the same when introducing a 3rd or 4th candidate. And it was performing well. But it did decrease at some point after adding more and more candidates and then the performance hit was much bigger.

I will reach out to my colleagues still working on this next week (long weekend ahead where I live). If we can reproduce this with a current Graal release, I'll share the benchmark – always happy to learn something new and if it is 'just' why out benchmark is broken. :)

<comment><USER27> I really appreciate the in-depth replies on HN. Something Reddit lost ages ago. I gotta say your posts have introduced me to a lot of dark magic in the JVM that isn't really documented anywhere unless you count source code. Much appreciated!

</comment></comment></comment></comment></comment></comment></comment></comment></comment><comment><USER44> Can anyone explain to me why this is a lot faster than j.toString() or String(j)

      for (let i = 0; i < 20; i++) {

        for (let j = 0; j < 1000000; j++) {

            `${j}`

        }

      }

I got

       Executed in  177.12 millis    fish           external

       usr time  159.12 millis   91.00 micros  159.03 millis

       sys time   17.96 millis  443.00 micros   17.52 millis

</comment><comment><USER45> I know this is supposed to be about python optimization. However, the post switches over to C at nearly the beginning of the process. Hence it really is about how to optimize python applications, by rewriting them in C (or other fast languages).

Which IMO, isn't about optimizing Python, apart from tangential API/library usage.

I've been under the impression that I'll get the best performance out of a language when I write code that leverages the best idiomatic features, and natural aspects of the language. If I have to resort to another language along the way to get needed performance, I guess the question is, isn't this a strong signal that I should be looking at different languages ... specifically fast ones?

Most of the fast elements of python are interfaces to C/Fortran code (numpy, pandas, ...). What is the rationale for using a slow language as glue versus using a faster language for processing?

<comment><USER46> > What is the rationale for using a slow language as glue versus using a > faster language for processing?

It's much quicker and easier to put together a Python program than a C/C++ program. The number of libraries out there for Python is incredible.

<comment><USER45> Honestly, this is subjective. I generally agree that some languages are well designed for fast prototyping, but not really great for computationally intensive production. I see Python in this role.

Note that when the original author wished to move this to a faster execution capability, they had to change languages. So they get the disadvantage of having to do the port anyway.

How does this save time?

</comment></comment></comment><comment><USER47> I really sick of such kind of benchmarks. I never have seen a real world python program that doesn't depend on any IO or doesn't have some C code behind wrapped calls. If your code is heavy with computations there are numpy/scipy libs that are very good at this. These optimizations bring < 10% of speed to real project/programm, but will require a lot of developers time to support it. If performance is the key feature and very critical, then likely python is not the right choice, because python is more about flexibility, ability to maintain and write solid, easy to read code.

<comment><USER48> Hard disagree.

Learning the tool you're working with means you know patterns to write generally more efficient code.

Even if you're going to use numpy/cython/cffi for faster submodules, writing faster code in general is a good thing.

<comment><USER47> I don't mind about knowing limitations. I'm saying that these optimizations usually are very hard tradeoffs and opposite side of it – code readability, speed of developer work, ability to maintain it working. I tried Cython and PyPy. Both are really good if your project is started with them, but if you decide to migrate to them in order to increase performance, it's like rewrite project to another language. Also both have a lot of limitations and cpython gives you still a lot more flexibility in decision making (choose frameworks, libraries and approaches how to solve certain problem)

</comment></comment></comment><comment><USER49> I would be interested in seeing the performance difference for f"{i}". My intuition is that it would be faster.

</comment><comment><USER50> That was really interesting, even though a lot of it (almost all the C) was way over my head. Thank you.

</comment><comment><USER51> Site broken, "Hug of Death"?

> This page isn't working

> blog.kevmod.com is currently unable to handle this request.

> HTTP ERROR 500

<comment><AUTHOR> This is what I get for hosting my own wordpress server. It was struggling and I installed a caching plugin which took down the site. Should be back up, sorry!

<comment><USER52> Installing plugins into Wordpress kind of feels like doing your own brain surgery.

</comment></comment></comment><comment><USER53> On my somewhat old Linux machine his main() takes 5.22 seconds. Meanwhile, this Julia code

      @time map(string, 1:1000000);

reports execution time 0.18 seconds. But that includes compilation time, if you use BenchmarkTools that runs the code repeatedly, I get 88.6 milliseconds

<comment><USER27> the outer loop runs 20 times as well

</comment></comment><comment><USER54> I'd be interested in seeing how PHP performs running equivalent code.

<comment><AUTHOR> I don't know PHP, but feel free to send a pull request!

<comment><USER55> Using range() in php it takes about 1.3 seconds. The PHP docs imply that it's a generator now but I'm not positive about that. I wrote an equivalent php function just using for loops and calling strval($x) 20 million times and on my laptop it runs in .9 seconds... The second form of creating 20 lists with 1m elements runs in .4 seconds. Without needing to write 300 lines of Py/C stuff. So... shrug? microbenchmarks? I guess writing the optimized code was the fun part and the actual benchmark/timing part doesn't really matter. It's just for loops... the other comments are right that benchmarks definitely matter when you're doing more varied "work" though.

For what it's worth, I did benchmark a big application in PHP "for real" and parsing a large configuration file (10,000 lines +) on every request did take up about %15 of the wall clock time. We optimized a few things there because it was worth it. It was a HUGE application of about ~1M LoC and an average request was about 300-400 milliseconds so... I guess it wasn't doing a lot of for loops?

Edit: I had a few minutes before my next meeting to code golf this so I decided to test if range() worked with yield [1]

      <?
      function gen() {  // 1.3s
        foreach (range(0,20) as $i) {
          foreach (range(0,1000000) as $j) {
            strval($j);
          }
        }
      }
      function foo() {  // .9s
        for ($i = 0; $i < 20; $i++) {
          for ($j = 0; $j < 1000000; $j++) {
            strval($j);
          }
        }
      }
      function bar() {  // .4s
        for ($i = 0; $i < 20; $i ++) {
          $x = range(0, 1000000);
        }
      }

      // [1]  returns in .06 seconds
      // pretty sure this just executes yield once and does no real work just like me this morning

      function gen2() {
       foreach (range(0,20) as $i) {
         foreach (range(0,1000000) as $j) {
            yield;
         }
       }
      }

</comment></comment></comment><comment><USER56> this is an area, nim could've come and improved on. i.e if nim had an ability to port your python code and have it running 98% on nim. most python users would have been there already.

</comment><comment><USER57> I've always assumed array iteration is more expensive you don't know the size of the objects and/or they aren't contiguous.

</comment><comment><USER58> hmm so complain and then say pypy is 7 times faster(and 4 times faster than nodejs)

<comment><USER59> I don't see a complaint, just an analysis of things that make code slow. Assuming that PyPy will just fix the problem is unrealistic, considering PyPy's limitations (doesn't support every architecture Python supports, lags behind a Python major minor version or two, etc.)

Python is a great language and PyPy a great tool, but let's not become complacent or - worse - dismiss valid information just because we like them..

</comment></comment><comment><USER60> [EDIT - posted in haste. I should RTFA]

Ctrl+F javascript - nothing.

At first glance this seems to be "dynamism==slow" but surely you need to explain why Python is slower than Javascript and for many years has resisted a lot of effort to match the performance of v8 and it's cousins?

<comment><USER61> If you had done ^F NodeJS, you would have found the relevant paragraph. Or, you can read the whole thing.

<comment><USER60> Yeah - I just went to delete my comment on the grounds I hadn't RTFA</comment></comment></comment>