A rant about PHP compilers in general and HipHop in particular.
blog.paulbiggar.com
blog.paulbiggar.com
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.
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.
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.
What should I use jQuery and not some other .js library? Because I know jQuery will still be there in a year.
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.
I expect that LLVM will be mature real soon now.
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.
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.
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.
"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.
In order to do anything resembling JIT, you would have to insert a runtime where one does not already exist.
- 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).
> 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.
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.
But as he says in this post, PHP code doesn't necessarily take advantage of these capabilities in practice.
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 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.
> 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.
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?
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.
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.
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.
Maybe we could have switched to something lighter, but the cost of all that development and testing would outweigh a handful of extra machines.
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.
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).
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).
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.
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.
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.
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.
Well, that is obscene.
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.
"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.