Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
A rant about PHP compilers in general and HipHop in particular. (paulbiggar.com)
146 points by pbiggar on Feb 8, 2010 | hide | past | favorite | 58 comments


Aside from the interesting technical details, the interesting part of this article is how much difference a brand name makes. He had difficulty getting people interested in PHP compilers, then Facebook announces one, and it's the new hotness.

This is a problem many startups have. Because people use brands as a shortcut to determine whether something is of sufficient quality to be worth taking the time to investigate, being a new unknown is a bit of a hurdle to overcome.


This is not just branding, but considering future prospects. No one wants to adopt a library and then have the project go dead.

What should I use jQuery and not some other .js library? Because I know jQuery will still be there in a year.


In this case I think having a built-in "and it worked" story helped a lot too. HPHP was released after already having a deployment that actually worked in the real world and saved resources/money, so it didn't have to go through the usual, "why should anyone care about a PHP compiler?" skepticism.


it's not really so much a brand name as just getting the concept out there. php developers don't realize that there are option when it comes to the runtime.

i would guess that the majority of them wouldn't even know to look for phpc or any alternative. there just hasn't been any news of something like HPHP before HPHP. if you want to run php, you go to php.net or install it from a package and go from there.

combine the above with very little actual need, very little public information on how to build a large site, and lack of maintainer interest and phpc just won't get major traction.

I would have loved to have found phpc several years ago when i was still working on a large site that had performance problems.


It wouldn't really surprise me if a lot of the people freelancing in PHP wouldn't know enough about the interpreter so see any advantage in compiled code.


a freelancer wouldn't know and if they did they wouldn't care. (i am a freelancer.)

we're talking about websites that are in the top 5% of all sites (by traffic) that would even need to think about using something like phpc or hphp. the number of those that would be willing to use a freelancer to handle their scaling issues are probably in the single digits at best.


On a tangent, I wonder why lots of people call it something other than "phc". I said phc a lot, its on the website as phc, but I've heard phpc, phpcompiler, and tons of variations. I'm curious why.


Well, I understand that completely. People trust brands. That is just a cognitive shortcut. That's why we have marketing.


I love this kind of meaty thinkpiece from someone who clearly knows what he's talking about. So rare.

I wonder if LLVM isn't getting mature enough. Yes, the Unladen Swallow folks are having trouble with it, but it sounds like the most important troubles are related to dealing with Python runtime realities, so those wouldn't necessarily apply to another runtime system.


Its amazing how close all of the scripting language run-time are, in terms of design and implementation. I think using LLVM with PHP would be worse, since the PHP interpreter is so badly written.

I expect that LLVM will be mature real soon now.


There were a few really interesting technical observations in this that made it worthwhile from my perspective.


PHP doesn’t really need a JIT. Server side programs in PHP don’t do a great deal of dynamic stuff, and it would be incredibly rare to load some random code at run-time, so a JIT wouldn’t be all that useful.

Uh, what? JIT compilers even improve the performance of C. Loading code at runtime and having a JIT are totally orthogonal.

If you're a language like PHP where you can't guess if a value is going to be a hash or the number 42 until runtime, JIT techniques are essential to get any reasonable performance. (Ah, here comes the argument, "PHP apps are never CPU-bound.")

It is odd that the article claims that JITs for dynamic languages are never successful; there is V8 and TraceMonkey, and Parrot optimizes Perl 6 pretty well. Java is fast because of its JIT. The author claims that LLVM's JIT compilation doesn't work, but that's untrue; LLVM's JIT works just fine for C. LLVM's C runs faster than GCC's statically-compiled C, anyway.


I'm making the point that you don't need a JIT to get good performance out of compiled PHP. A JIT would be useful, but not required.

> If you're a language like PHP where you can't guess if a value is going to be a hash or the number 42 until runtime, JIT techniques are essential to get any reasonable performance.

Not true, according to my research. You can pretty much tell the type of a value at analysis-time in PHP. Its expensive analysis though.

It would be useful to be able to make some assumptions and back them out at run-time though, like assuming that the sum of two integers is an integer.

> It is odd that the article claims that JITs for dynamic languages are never successful; there is V8 and TraceMonkey, and Parrot optimizes Perl 6 pretty well. Java is fast because of its JIT. The author claims that LLVM's JIT compilation doesn't work, but that's untrue;

I'm not sure where any of this is coming from. I never claimed these things.

I've never seen performance statistics for the LLVM JIT. Link? I've also never seen stats where LLVM beats GCC in execution time of compiled code.


"I've also never seen stats where LLVM beats GCC in execution time of compiled code."

http://leonardo-m.livejournal.com/73732.html

http://www.phoronix.com/scan.php?page=article&item=apple...

Results are mixed, with llvm-gcc generally being slightly slower. There are some benchmarks where it's much slower, and also a few where it's actually faster than GCC. So yeah, there are tasks where LLVM beats GCC in execution time of compiled code.

Rumor has it that parts of Snow Leopard are built with llvm-gcc. Presumably Apple would benchmark both alternatives and only build the parts that are actually faster with LLVM.


LLVM isn't used as a JIT for C in any existing compilers (clang, llvm-gcc, etc) as far as I know; It's used to emit static object code. (If I'm wrong, of course, I'd appreciate a pointer to the projects)


clang and llvm-gcc are two front-ends which translate C code into LLVM intermediate representation. LLVM itself optimizes that IR and emits (statically or at runtime) object code, C code, etc. An example of using LLVM to JIT C code can be found in this presentation by Nate Begeman: http://llvm.org/devmtg/2008-08/Begeman_EfficientJIT.pdf at the 2008 LLVM dev meeting. There is video linked off of this page as well: http://llvm.org/devmtg/2008-08/

There is less incentive to JIT languages like C because so much can be determined statically, unlike more dynamic languages. It can still buy you something if you use runtime specialization to e.g. make different, optimized versions of functions based on the values of the arguments.


C++ is more appropriate for JITing than C, especially if you do a lot of stuff with virtual methods. That said, the cost of a virtual method call is a lot less than a typical dynamic method invocation in a dynamic language, so the cost savings aren't as huge, except maybe in inner loops.

However, these days new C/C++ apps seem restricted to domains like embedded systems, real-time, and game development, where memory usage (no virtual memory) and performance consistency is very important. So a JIT doesn't make a lot of sense, as it introduces a lot of variability into the application.


"C++ is more appropriate for JITing than C" Agreed.

"However, these days new C/C++ apps seem restricted to domains like embedded systems, real-time, and game development, where memory usage (no virtual memory) and performance consistency is very important. So a JIT doesn't make a lot of sense, as it introduces a lot of variability into the application."

Probably also true, but a JIT can be useful for loading code at runtime and supporting a wide variety of hardware without shipping a fat binary with a bunch of different versions. Graphics drivers contain JITs for OpenGL/shader language code, and Apple uses LLVM JIT to generate optimized code from GLSL shaders that takes advantage of all available hardware capabilities without a sea of #ifdefs ( http://llvm.org/devmtg/2007-05/10-Lattner-OpenGL.pdf ). Your point about memory usage and variability is well-taken though.


Dunno what the defaults are, but I'm fairly sure I've compiled C applications with JIT enabled.


I'm not sure what that would even mean. JIT is an optimization for virtual machines. It compiles VM code (such as Java bytecode) or interpreted code to machine code at runtime. C already compiles to machine code.

In order to do anything resembling JIT, you would have to insert a runtime where one does not already exist.


LLVM's compiler suite compiles C to LLVM bitcode, and the LLVM (low-level virtual machine) runs that code.


I don't think LLVM provides any such virtual machine, despite its name. It has a code generator, but no VM as such.


I'm almost positive that LLVM provides both. You always target LLVM bytecode with your frontend, but then you can choose either to:

- run the bytecode using the LLVM with or without JIT.

- compile the LLVM bytecode into x86 bytecode.

This is what clang does (afaik) and is a reasonably nice use case because you write a static language that targets LLVM bytecode and end with a fast program because LLVM will output optimized x86 binaries.

However, I'm pretty sure that the efforts like unladen swallow attempt to use the LLVM's virtual machine in the hope that it will become very fast as they optimize it (especially the JIT part).


Yeah, you just instantiate an ExecutionEngine and feed it your LLVM bytecode, and it'll give you back C function pointers that you can call. The ExecutionEngine is either a JIT (which'll compile your bytecode into native code and give you that back) or it's an interpreter (where the function pointer you get back is an entry point into the interpreter).


Ok, my mistake, sorry.


No, there isn't any support for that in the C frontends that use LLVM. They all generate direct assembly code. Feel free to look at the clang and gcc-llvm source to verify.


he's saying JITs are very hard to implement


I believe pretty much anything Paul Biggar has to say about dynamic language interpreters / compilers. His talk at StackOverflow DevDays London about dynamic languages was a great eye-opener for me and a fantastic experience.


I was at the same talk, but I got the impression that because his perspective comes from trying to do static analysis and optimization, he doesn't fully appreciate some of the capabilities of dynamic languages. The overall effect was a static typer missing the point.

But as he says in this post, PHP code doesn't necessarily take advantage of these capabilities in practice.


Yeah, PHP isn't Python or Ruby, which are properly dynamic. I think those languages are too far gone in their particular dynamic direction to be saved. But I'm convinced you can build a language that feels dynamic, but whose meta-programming is largely compile-time. Even the funky Python I see (metaclasses and decorator) could largely be compiled if the language rules were slightly tweaked.

I'm interested: which capabilities don't I appreciate? (BTW, my background is very much being a static-typer, missing the point, and slowly realizing it).


First off, I'm pretty much a static typer myself, so I'm not necessarily the best advocate for the dynamic case. But by the same token, I can argue against some of the problems of static typing.

First up: I'm talking about object-oriented approaches, with polymorphism and dynamic dispatch being the core abstraction tool.

If you try to introduce more static typing into a system with polymorphism, you're trying to push type decorations so that they can follow where the expressions go a little further, so that you can reason more. Parametric polymorphism, or generic types and methods (hence forth called generics to avoid confusion with OO polymorphism), are a key tool. They let you better reason about values of which types cross method boundaries. With generic methods, you can usually infer the type parameters. With generic types, the type arguments can serve as extra documentation. A Java ArrayList<E> seems clearly superior to ArrayList at first glance.

But all sorts of problems start coming up when you mix polymorphism with generics. Type inference isn't so simple anymore, as ad-hoc choices need to be made. Instantiations of generic types have no necessary subtyping relationship. So you have to reintroduce dynamicism in the back door via base classes or interfaces etc. that use a top type (java.lang.Object or whatever) to re-unify types; or you dabble in variance. And you want to do more with your arguments of type parameter types, so you bring in type parameter constraints, or type classes, or other means of generically handling values.

So perhaps you have use-site variance or declaration-site variance combined with type classes combined with polymorphism. Suddenly using or declaring generic types has gotten quite a bit harder. Getting type information to flow into the right places starts requiring confusing tricks, particularly when you want e.g. the methods of your ancestor to return a type related to the concrete instantiation type, for example something like this in C# syntax:

    // so you want a GetThis method that returns a correctly
    // typed thing for the current instance
    class Base<T> where T: Base<T> { public T GetThis() { ??? } }
    class Desc: Base<Desc> {}
When the static typing advocate has tangled himself up in a knot like this (or deeper, e.g. when you're passing other type arguments around across multiple classes and hierarchies, just to get the damn types right), the dynamic typer shakes his head, and says "don't you see - it's all just objects!".

One of the paradoxes is that declaratively specifying, at compile-time, all the types that may reach different parts of your program at runtime, is harder than writing the program to build the object graph at runtime in the first place, and treating the occasional runtime type error as a bug that will be eliminated.

Now to the other side of the story, meta-programming. I agree that you can do lots of meta-programming at compile-time, but there are two problems, as I see it. One: compile-time is too early to be making decisions. What do you do when you can't afford to restart the process, but you still need to change the running code and how it works? This isn't a theoretical concern; I've architected such a server system in the past. It could keep on handling requests relating to client sessions associated with a previous versions of the server-side behaviour, but new client sessions would use the latest version of the server-side behaviour. Rolling over the server farm from one version to the next didn't require tricks with load balancers and restarting app servers, etc.; it was all built into the system.

(Actually, the architecture was really interesting, and I should write it up in more detail one day. Another key fallout of this approach was that the session data, which was (usually) stored on disk and was a bit like a Lisp or Smalltalk image only typically 20K in size, had a pointer (a URL) inside it telling the server where to get the behaviour for this session. This meant that problem sessions could be post-mortemed quite effectively: the old session could be resurrected, stepped through, etc.)

And the second problem of compile-time metaprogramming: the formalisms you use to munge with the types, particularly static types which the compiler will later infer further things from, may in practice be quite different to the imperative constructs programmers are used to using to implement behaviour. When they want to add a method to a class, they want to do it using the same tools (or close) to that which they'd use for adding an item to a list or hash table.

Perhaps the meta-programming is implemented as a plugin to the compiler, or a macro system, where the code is modifying the compiler's own definitions of the types etc. But now we've introduced an abstraction level into the chain of source code -> executable text that most programmers are not familiar with, and may be intellectually ill equipped to deal with - not because they're stupid or incompetent, but rather because they simply don't need to know. Consider e.g. hygienic macros and the problems of which symbol refers to which level of abstraction, the compilation level, or the runtime level; the distinction between the level you're at when you're quoting and unquoting, in Scheme terms. Consider the confusion of this poor fellow whom I helped out:

http://stackoverflow.com/questions/326321/how-do-i-create-th...

When you consider the tangled mess of static typing you can get into when you mix polymorphism with generics, and then add in the ability to munge with code and types such that the compiler sees the modified code / types, and hopefully you can see it's easy to add more complexity than the benefit you get out.


> I’ve heard the argument "you don’t need a compiler, since PHP is rarely the bottleneck" for many years. I think its complete bollox. ...

> Unless your PHP server is sitting there idling (which is probably the case for many PHP servers out there) ...

Don't these two statements (abbreviated in my quote above, but directly one after the other as quoted) directly contradict each other? My personal experience is also that the CPU is never the bottleneck in (well written) PHP apps. You're usually always waiting for the DB or the network/io latency of something else like memcache.

In fact, I know at my last job (handling ~100s of millions of dynamic PHP requests/day) that PHP's gzip compression was turned on specifically because we had gobs of spare CPU, so using it to save bandwidth was a win both for speed and for the bill.


Here we go again.

If you have lots of spare CPU on machines in a web server farm, you have too many machines. Even though I/O is the bottleneck for any single request's response time, CPU is generally the bottleneck on total load.


Actually, in that same situation, I believe it was limited by memory (MB per apache+php process), and number of processes available driven by latency. Given the number of processes running on a machine, and the memory that each consumed, we "couldn't use up" the processor capacity, the memory ran out first.

Maybe we could have switched to something lighter, but the cost of all that development and testing would outweigh a handful of extra machines.


that's a double edged sword though. if you don't have enough spare cpu, you won't be able to handle any sort of spike in usage. say from an advertising campaign that works a little too well or if a server goes down.

and once you start maxing out boxes, the load comes even harder since the site loads slower. you very quickly run out of resources and suddenly you're SOL and everybody's mad that the site won't load.

honestly, it's hard to point at any one source of problems when scaling a site. cpu, ram, and i/o are all equally problematic. and if you under plan any one of those it can take your site down into a spiral of doom.

you really must allocate your resources for peak load, not for average or nominal load. and then allow for some failures.


Sure. But under max load, that gzip will be adding to your problems.

And of course, graceful failure is an art in itself. You have to start turning down requests before you actually hit peak capacity, or you risk getting stuck in a thrashing pit.


I am NOT very informed about server tweaking, so please add a pinch of salt as appropriate... But aren't there two related issues to consider? Assuming a simplistic two-tier architecture consisting of a UI/domain logic server (in PHP) backed by a database, the first question is whether the speed of the PHP bit affects the total load the server can handle. Presumably if it is idling waiting for the database, you could handle some more connections and need to work on optimizing your database until you reach a sort of equilibrium where both are maxed out.

The second issue is latency. Even if it spends time waiting for the database, the amount of time processing each request affects the amount of time a client waits for a response. Even if the database is the limiting factor on the number of simultaneous requests that can be handled, you may still want to increase the speed of handling each request to make the UI snappier.

Am I whistling in the dark here?


No. And even when you have lots and lots of concurrent I/Os going in a blocking server design, CPU becomes a different kind of constraint: it governs how fast the kernel can context switch.


I don't see the contradiction...


The guy seems petty. The name of the tool is hphp, if you drop in an i and an o, you have hiphop, which is a perfectly fine name considering Django and all the other music named tools. It is my belief that the dislike of the name comes from a dislike of the type of music.

And rewriting a negative comment on your blog? That's just not done. Delete it, but don't rewrite it ever.


> And rewriting a negative comment on your blog? That's just not done. Delete it, but don't rewrite it ever.

Why not? Its my blog. Its not like I hid it. Ultimately, to cultivate a good community, you have to have strict rules. Now I'm not exactly curating a massive community here, but it seems like an effective way of discouraging the kind of dickheadedness that was his original comment.


It's your blog, but it's not your comment.


That's an interesting perspective. Does the poster "own" the comment on my blog? Do I? Since he has no control over it, is it mine? Is it still his if he posted anonymously? Does the fact that he put my email instead of his in the email box hand control over to me?

All interesting philosophical questions. However, since I have editorial control, and determined that he was acting anti-socially, it seems perfectly fine to me to scribble over it (though I could see a problem if I wasn't transparent about it).


People own their own creations. The power owns (the copyright on) his comment even if he posts it to your blog, unless you've made an agreement otherwise (e.g., in your blog's terms of service).

You control what your blog displays, though. There is probably a technical legal difference between deleting a comment (definitely right and legally permissible) and modifying a comment (thus creating a derivative work of a copyright work which you may not be licensed to derive from).


> And rewriting a negative comment on your blog? That's just not done. Delete it, but don't rewrite it ever.

I guess I should point out that it wasn't so much negative as a complete adhominem, name-calling, attack. I wouldn't have scribbled over a simple negative comment.


I think this person is not really informed about JIT.

Knowing the code at the beginning of the run doesn't make you know its runtime profile, at all. But it's something the LLVM generation is not aware of.

I think that the people who saw only the suits in front of java and C# didn't see what happent on the JVM/CLR side. And a lot of things where shaken there.


I'll admit I haven't ever written a JIT, but I wouldn't say I'm not informed about them. Its a question of tradeoffs: JITs can help you get good run-time performance if your types are static for some large portion of run-time, but not really known at compile-time. I would allege that they are available at compile-time. I think my research showed I could prove a type for about 60% of variables/fields/etc, and tie it down to {int,float} or {object,null} in most other cases. I guess I forgot to put that in.


You are speaking about monomorphic specialization, in the JVM benchmarks they are at 80% of specialized code (correctly guessed concrete type), moreover, the JVM keeps the code for various specializations if it needs too. And the good part is that they don't do all this stuff if it's useless.

The entry into JIT is stiff, but you already spent 4years on your static compiler, so basically you had this time to make/reuse/spacialize a JIT interpreter that would just be a drop in replacement.


I would understand the term "monomorphic specialization" to mean "can be many dynamic types, but in practice only has one dynamic type". What I'm implying is that the vast majority of values only have one static type, if your static analysis is good enough.

The question of "which is better" wasnt really a factor in my decision to build a static compiler instead of a JIT. Building a JIT as a solo effort for a complex language like PHP would probably have prevented me doing enough research to get a PhD. More importantly, I wasn't interested in JIT, I was interested in static analysis and (static) optimization.


> The PHP interpreter is also quite memory hungry, as interpreters go. Any PHP value in your program uses 68 bytes of overhead [6]. An array of a million values takes over 68 MB.

Well, that is obscene.


Python (and probably every other dynamically typed language that does use the tagged pointers trick) has the same issue, even worse on 64-bit. Allocating [sys.maxint-1 for x in xrange (1000 * 1000)] takes up 36 megabytes of memory.

Allocating dict((x, float(sys.maxint-1)) for x in xrange (1000 * 1000)), which is closer to PHP's combined arrays-dictionaries and values which I believe are always floats (unless I'm thinking of Perl or Javascript?), takes up 97 megabytes.

Of course if you really need 1 million floating point values, that are not going to be None and that are contigously indexed 0 to 999999, you could do: array.array('d',(float(sys.maxint-1) for x in xrange(1000 * 1000))) which has no overhead.

But for a random collection of objects with attributes and lists and dictionary, the overhead exists. I've got 15 gigabytes of Python objects on this box across a couple dozens of processes and sometimes I dream of a time where every bit was accounted for, where for every bit I could trace back to where it was set and why and accounted for it.

As for the "tagged pointers" trick -- basically taking advantage of that pointers to objects are going to always have the last bottom bits set to 0 and saying that if they are set to 01 for example, that the top 32 or 62 bits are an immediate integer -- I think someone tried it out in Python a few years ago and the result were that it was no win overall, though it might have decreased memory usage occasionally.


I was thinking more about the overhead of building arrays of small strings. Depending on how you measure memory usage, Python is 3-4x more efficient at building lists of one-byte strings than PHP is wrt memory usage.


Python's float type is a double.


I'd really like to see the source of that overhead. I'm looking at the Zend source and only seeing 16-20 bytes of overhead per `zval`, depending on alignment.


OK, I misspoke. I'll quote from someone who knows this stuff way better than me:

"As a result of the function-local symbol-table, each PHP variable uses a great amount of space. The zval itself is 16 bytes long. However, the symbol-table is a hashtable with a 36 byte bucket. Combined with memory allocation overhead, each variable occupies 68 bytes on a 32-bit platform [?], and nearly double that on a 64-bit platform. "

- me, sometime within the last two years.


Ah, great -- thanks. I found the fuller email whence that came at http://www.mail-archive.com/internals@lists.php.net/msg35235... , in case anyone else is interested.


Is there anything particularly novel about HPHP that would be patentable?


I don't know. I don't think there is enough information released about it.




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

Search: