[This post is for programmers, especially AP programmers. All others read at your own risk. To McDuck and Refusal2, the judges—Chief Justice Cartwright, Associate Justices Marvin and ExhibitCAA—would like to extend a genuine appreciation for your willingness to put yourselves out there. It takes some courage to expose source code to the world! We were entertained, and we learned a lot. As the freshman, I was assigned to write the opinion.]
October 16 was a typical day on the Internet. A geek lamented: “C is a pain. Python is too slow.” Amen, brother, there’s no shame in getting old, but it’s no fun! But if you’re looking for support on Blackjack The Discord, you’ve come to the wrong place! The conversation devolved into a flame war involving primitive types, mockery of the “perceived speed problem,” and technical explanations of dict support.
Unlike the Internet “debates” among trolls in the Comments section of Yahoo News articles, debates in the AP community are sometimes settled by the purest melding of free speech and capitalism: a prop bet. We expected this one to be a bit dry compared to Brian Zembic’s legendary tale, told by Michael Konik in The Man with the $100000 Breasts, but oh, were we wrong! These are some actual quotes from the high drama that ensued:
McDuck: “I used a fibonacci hash table as a cache in my C++ combinatorial analyzer”
Refusal2: “dictionaries have a hard floor of O(log2) speed”
McDuck: “stop being an obnoxious know it all”
Refusal2: “hahahhahaaaaa ha haaaaaaaa ok.”
Bystanders were unable to separate the two, and Ryemo’s “Algorithm” actually incited them, leading to the genesis of the bet after FOUR DAYS of this back-and-forth, when McDuck threw down the gauntlet: “I am the GOAT of performance optimizations. Python is an inherently slow language without some ugly libraries that avoid using the python interpreter.” Refusal2, not surprisingly, came back with “numpy is the GOAT.” And I thought I saw an “Infinity plus one!” in there, too, but the Algorithm removed that post under its hate-speech guidelines.
The original dilemma was that McDuck’s Python program to compute the edge on the Dragon7 took around 41 seconds to simulate 100000 baccarat shoes. Refusal2 claimed that a significant speed enhancement was possible, perhaps reducing the runtime to 5 seconds (or 1/8 the time, if run on ancient hardware where both programmers’ code is slower). Since a live-money bet was becoming a tangible possibility, a frenzy of goal-post moving ensued.
The agreed upon wager excluded parallel processing, “ugly” libraries (which mainly meant obscure libraries in C/C++, I guess), the Numpy library specifically, the extent of code increase, and the Numba compiler. For a simple program that involves no network traffic, no user input/output, and no disk access, those exclusions eliminate most of the tools that a programmer would typically use to speed up a program. But the bet must go on!
The logistical problems were settled when MPS (not his real handle) agreed to be the escrow agent to hold the wagered funds (online prop bets are an important use case for crypto!), provided a panel of experienced programmers would volunteer to render a verdict on the bet itself. Both bettors agreed to a three-judge panel: Chief Justice Cartwright, and Associate Justices Marvin and ExhibitCAA [me].
Both programmers posted their code on the Discord page. MPS verified receipt of the funds (and McDuck had agreed to 5:1 payout odds), and the judges retired to their chambers to deliberate. The immediate thought of the entire panel was “What did we just step in?” Prop bets, especially within a small community where we all know each other, run a great risk of offending someone, the loser of the bet for starters. Keeping the stakes modest is critical to avoid hard feelings. (AP maxim: Don’t gut the fish before they can reproduce!)
But, none of the judges were able to control their voyeuristic curiosity about OPC (Other People’s Code). Looking at source code is exactly like watching a Paris Hilton sex tape. The opportunity to watch someone you know doing a very private thing can be wildly entertaining, and educational, with possible reactions ranging from “Whoa!-I-didn’t-know-that-was-a-thing” to “That’s-disgusting.”
For my part, I wanted this incident to serve as a proof of concept, to show a way to implement prop betting in the blackjack community. I have long thought about an AP Arbitration Panel, mainly to settle chop disputes, and thought that this could serve as a model for that as well.
When we opened up the code, it was apparent that the issue of extreme increases in the amount of code was a moot point, as Refusal2’s primary change was to run the source code using PyPy, a particular implementation of a Python environment. The next task was to check whether Refusal2’s code met the benchmark. Once McDuck learned that PyPy was being employed, he announced that his own source code beat the benchmark time (5 seconds) on his own hardware, but the die had been cast.
Though this almost conceded the benchmark, the judges used their own hardware to test Refusal2’s code. Cartwright ran the program in 4.7 or so seconds, with an obscene time of around 3 seconds when he did something or other. But that’s why he’s the Go-To Justice, and though I got a new Surface Pro 8, Justice Cartwright clarified that soon he will have a new computer, one far younger and more powerful. Marvin ran McDuck’s program in 40 seconds, compared to 6.4 seconds for Refusal2’s version. This doesn’t quite meet the benchmark, but Marvin has a 2017 machine, which is two generations behind according to Moore’s Law of computing power. My own machine clocked at around 7.5-8 seconds for Refusal2’s program, but I’m on the road using a laptop from 2012, and the bettors had agreed that ancient, fake hardware didn’t count. And I took that personal.
After visiting three local Best Buys (one of which turned out to be someone’s house), I treated myself to a new Surface Pro 8 (keyboard sold separately!). I’ve never used Python, but installing PyPy was the easiest part of setting up my new laptop, with free download and documentation on their website, which claims that PyPy is the #2 most widely used Python implementation. So using PyPy is not excluded by the “no obscure or ugly libraries” clause of the prop bet.
With my new laptop, Refusal2’s program ran in a range from 4.52 to 5.21 seconds. If I simmed 5 million shoes, then it would clock in at 3.6 to 4.97 seconds per 100000 shoes, always under 5 seconds. And though new, my laptop with its Intel i7 chip is still slower than Cartwright’s i9 chip, or the typical desktop hardware that a speed-driven programmer would be using. Since Marvin’s 6.4 seconds is close, and used the oldest hardware of the group, and he’s in the minority, the panel considered Refusal2’s program to have achieved the speed benchmark (though it was close, since Refusal2’s program would have failed had the line been set at 4 seconds).
Assuming there would be a debate over the use of PyPy, we decided to examine the code to determine whether the benchmark could be achieved without PyPy. A few things stood out from examining both programs:
- Neither programmer has much real-world baccarat experience. Both programs violated real-world game conditions in ways that are relevant to card counters and APs (though the computation of the off-the-top Dragon7 edge here would not be impacted).
- McDuck’s program wasn’t optimized whatsoever. There was literally nothing in it that was different from the most straightforward coding implementation a student in a Python class would write. He would likely say that he didn’t try to optimize it, but the court was hoping to be impressed, and was not. But the code would be ideal as the “vanilla” benchmark to measure how fast an un-optimized program would run.
- Both programs lacked some or all of the obvious micro-optimizations that might not make a big speed difference, but which would be kneejerk coding for a programmer with optimization in the blood. In particular: (a) The card array can be initialized with 0 for the T J Q K, instead of 10; (b) After (a), the modulo arithmetic can be replaced with a single subtraction of 10 when necessary; (c) Summing initial hand totals over a card array is unnecessary when we know there are always just two cards; (d) The final total needs to be updated only when there is a single hit, and again can avoid arrays; (e) Computing the Dragon7 payoff can be done inside the hitting loop, which is the only time a winning payoff could occur.
- As in a real-world casino, there isn’t much that a programmer/dealer can do procedurally to speed up the dealing of cards and the addition of hand totals, since they are simple, elemental operations. For the dealer to deal more cards per minute, the only way is to willfully “go faster” which is akin to getting a faster CPU for a simulator. The shuffle is where some complex, slow operations might be streamlined. McDuck used the canned random.shuffle() method to shuffle the deck in place, while Refusal2 used random.sample() to generate a permutation, and then a secondary array to retrieve the card values corresponding to that permutation. After some small amount of testing, I believe that random.shuffle() is slightly faster, but I’m not sure, since run-time fluctuates by about 10% each time I run these programs.
Some experimentation with different versions of Python led to Chief Justice Cartwright’s conclusion that without PyPy, Refusal2’s program would be 10-15% faster than McDuck’s. Based on ExhibitCAA’s tinkering with micro-optimizations and various timing, the court inferred that without PyPy, Refusal2 would be unable to reach the benchmark, even with further optimization possibly allowed before the deadline.
So, the issue boils down to: Is the use of PyPy permitted within the terms of the bet, which allows the use of a “Python interpreter.” Foreseeing this non-trivial issue and possibly a few others is why I lobbied for the “Push” option for the judges, Both bettors played the semantic game fiercely, pointing to various Internet sources: Wikipedia saying that PyPy is an interpreter and a Just-In-Time compiler. A different Wikipedia page saying that an interpreter generates and optimizes code that is machine-readable. The PyPy web page itself saying “PyPy is a Python Interpreter.” McDuck at one point said PyPy was both an interpreter and compiler, but tried valiantly to move goalposts and later say that it’s a compiler.
Now come the judges. To assuage the bettors’ regret that maybe they didn’t find “the right Internet source” to sway the judges, the court asserts that no particular web site was relied upon to provide a definition. Judges were selected for their programming experience, and that experience was used to interpret “interpreter,” in addition to a week’s worth of Discord testimony to inform whether there was a meeting of the minds in the contract of the bet. We started by looking at what the bettors both immediately excluded: Numba, which was deemed to be a compiler. If Numba was excluded, it is informative to understand why, and compare PyPy to Numba in those critical areas. So we researched Numba. Numba requires the programmer to annotate the original source code to indicate which procedures should be compiled. The default option when doing so is the “NoPython” option, which removes the role of the Python interpreter, and there is also an Ahead-Of-Time feature that can create python modules that can run on machines that do not have Numba installed.
The idea of a compiler is simple: The computational cost of interpreting human-readable source code written in a high-level language like Python, and converting that into a machine-executable form, should be paid only once. We shouldn’t interpret source code with each and every run. This is the same idea of using a lookup table for a complicated computation. Let’s do it once, and save the result, and thereafter use the pre-computed result, which is instantly available. Numba, and more-so a full-service compiler, does that. The compiler takes source code as an input, but then produces an output that is devoid of the high-level Python, and no longer human-readable. But most machines can read it now. Critically, the compiler has produced an executable file that is, within limits, platform independent.
PyPy is quite different from all of that. PyPy is a single-installation environment (Numba requires secondary installation following the basic Python install) that requires no modification to the original source code. The user/programmer doesn’t need to learn any new software or syntax. PyPy allows a user to type commands directly at a command line, or execute a pre-written text file of source code. The cost of interpreting source code and converting it to a machine-executable form is borne with EACH AND EVERY RUN of the program, unlike a compiler. PyPy is unable to produce a platform-independent, standalone executable devoid of Python. Their documentation explains that this is a technical impossibility, due to the way PyPy handles internal addressing in memory during runtime. So the only option is: The programmer gives PyPy the commands in Python, and PyPy spits out results. There are no extra steps, nor can you skip a step to make the program faster next time. To label it a “JIT Compiler” is more marketing than anything, since the user doesn’t need to know what PyPy does under the hood, and doesn’t care. Unbeknownst to most users, the standard “Interpreter” CPython makes auxiliary files (possibly hidden) with the extension .pyc and saves them to your disk. These files contain “bytecode” that Python can use to speed up the program on subsequent runs. Even professional programmers, including the judges, don’t really know what PyPy’s “JIT Compiling” really does at a technical level, and don’t care. From the perspective of a programmer or a user who can’t see inside that black box, you give it Python code, and it runs it, and produces the output. If we must give that a single label, it would be “Python Interpreter.”
Consideration was given to deactivating PyPy’s JIT feature, if PyPy allows that option. While doing so would unequivocally win the bet for Refusal2 if the speed benchmark could be achieved, that doesn’t imply that we must deactivate the feature for Refusal2 to be eligible. Interestingly, deactivating the feature resulted in a time that was slower than “standard Python”—indicating that PyPy’s processing is optimized to work in conjunction with the so-called JIT feature.
Upon learning about the use of PyPy, McDuck made a few arguments why PyPy should be disallowed. He made an ad hominem argument that employing PyPy was a “Gotcha” move, because Refusal2 could have mentioned days earlier that he intended to use PyPy. Even if Refusal2 is attempting a Gotcha, and could have forewarned McDuck, the onus is not on Refusal2 to REFRAIN from a Gotcha attempt. These are prop bets! In some AP circles (poker), Gotcha is the name of the game.
McDuck further stated that obviously PyPy is not an interpreter; otherwise, he wouldn’t have offered 5:1 on a prop bet. But regardless of the odds payout, the same logic could be used by the other side: Obviously PyPy IS an interpreter; otherwise, Refusal2 wouldn’t have wagered $1000 of his money on it. McDuck’s argument boils down to: “I can’t have lost the bet, because then I wouldn’t have made the bet.” This circular argument precludes the possibility of anyone ever achieving a Gotcha victory, and perhaps precludes most wagers in general.
These arguments were deemed disingenuous. The reason McDuck didn’t specifically exclude PyPy, as he had excluded Numba, and the reason he made the wager at all, is not because he considered it obvious that PyPy would be disallowed under the interpretation of “Python interpreter”; rather, McDuck didn’t specifically exclude PyPy for the simple reason that he didn’t know about it!
If he had known about PyPy, then the excluded list would have been Numba/Numpy/PyPy from early on. If he had known about PyPy, would McDuck have ever cited his flaccid 41-second runtime on the original program? We think not.
Seeing Refusal2’s solution might leave some readers saying, “I feel so unseeatisfied.” Perhaps they were hoping for some cunning sequence of code that would turn out to be the solution to the Byzantine Generals’ Problem, or turn out to be the proof of Fermat’s Last Theorem once all the pieces came into focus. That would have been quite entertaining and impressive, but in the real world, we want results. Don’t we want the program to run faster in the way that is simultaneously the most convenient for the programmer?
A client who hired a programming consultant to speed up a program should be thrilled, not disappointed, with this PyPy solution. This solution does not depend on some tricky code, or consultant’s black box that would prove difficult or costly to maintain. This solution is simple and free, with credibility and safety deriving from its mainstream usage. This solution requires no modification to the original source code. And, the solution has wide applicability beyond this specific problem. I, for one, intend to use PyPy for any future Python code that I write.
In the sense of providing a legitimate solution to a real-world programming problem, Refusal2’s solution isn’t a Gotcha at all. McDuck didn’t know about PyPy. Refusal2 did. PyPy is a very useful tool. While the field of programming is too vast to be an expert at everything, knowing the macro tools available IS an important part of a programmer’s expertise, just as game selection is an important aspect of being an AP. Modern CPUs, RAM, design architecture, and optimizers have made a lot of the micro stuff irrelevant. In the old days, we tried to reduce the number of multiplications by nesting polynomials, or use Gaussian quadrature to do our own numerical integrals, and stuff like that. These days, the meaningful speed increases often do relate to the macro tools in use: cloud computing, faster networking, parallelism, SSD drives.
So Refusal2’s Gotcha is not in the programming solution provided, but rather in allowing the ambiguous wording of the bet. There simply isn’t a consensus on what “Python Interpreter” means in the modern era, and while the judges lean towards PyPy being eligible as a “Python Interpreter,” and therefore Refusal2 does not lose the bet, we aren’t willing to debate the semantics with “the Internet” ad nauseam.
So, it is the verdict of the panel that the prop bet itself is a PUSH, but that Refusal2 should be awarded costs, for providing a legitimate programming solution that was new to McDuck.
So, to the issue of the consultant’s costs/fees. The court felt that Refusal2’s claim of putting $5000 worth of labor into this was excessive, given that most of that time was spent haranguing McDuck, or haggling over terms of the bet, not actually offering meaningful consulting work. And while the court agreed that the conferred knowledge would be worth thousands of dollars to McDuck in the future, Refusal2 has no equity or commission claim on those benefits. On the other side, McDuck’s claim that a complete baccarat simulator can be banged out in 15 minutes is unrealistic. (Was it Lincoln who said, “If you have an hour for programming, spend 15 minutes writing the code, and then 45 minutes bragging about how fast you wrote it.”) It took me an hour just for Windows to download and install all the updates on my Surface Pro 8. Installations, tinkering with optimizations, investigating solutions that turn out to be dead ends—these are all part of a consultant’s billable time. For this project, we felt that 3 hours was a reasonable amount of billable time.
The AP world doesn’t have a huge labor supply of professional consultants charging publicly known hourly rates, but we tried to comp a few sources. I never get paid for my numbers work, but I’m not the Go-To Guy (the efficient-markets school would have a simple, depressing explanation for those two facts). A programmer on eLance with good reviews who is in a US time zone will cost $50/hour or more. Justice Cartwright used to command $200/hour when he was a lowly, lowly telecomm grunt. Bob Nersesian and Thea Sankiewicz, lawyers to the APs, used to charge $300/hour, and that was years ago when they weren’t full-blown fat-cat lawyers. I’ve not heard a number less than $100/hour for the Wizard, and I thought I had heard as much as $500/hour for some numbers work he had done. I wanted to hire an academic once, and he wanted $500/hour, and a machine-learning poser I know got around $1000/hour for a recent gig.
Ironically, McDuck’s attempt to set up a different wager (on poker-chat, 20211006) gave us the most telling assessment of his own time value: “I don’t get out of bed for less than $400/hour these days.” Any motion by McDuck suggesting that his time is more valuable than Refusal2’s time is summarily dismissed. So, the court’s unanimous view is that Refusal2 is to be awarded costs in the amount of $1200 (3 hours x $400/hour), and the remainder of the prop bet is dismissed with prejudice. The court clerk MPS is ordered to disburse funds accordingly, using previously agreed upon conversion rates and fees (i.e., see you in Zihuatanejo, MPS!).