Thanks to visit codestin.com
Credit goes to github.com

Skip to content

How to Optimize a Primitive

Jeremy B edited this page Feb 12, 2020 · 5 revisions

The instructions seen here are based on my (@TheBizzle) experiences in submitting this pull request. They have been updated to reflect changes made to code structure since that pull request. The pull request involved taking the existing implementation of in-radius and hiding it in an _inradiusboundingbox primitive class, providing a straight-forward (and sometimes faster) default in its place (_inradius).

First things first: If you're planning on optimizing a primitive that is in prim.etc, you should start by lifting it up into prim. The compiler is not allowed to know about anything in prim.etc, and the SBT depend task will stop you if you even try, so just lift that bizz right away.

The next step is the actual implementation of the optimization. This consists of creating an optimized primitive class, also within prim (since the compiler's optimizer needs to know about it and use it). At this point, you might run into problems with "rejiggering". You'll know that you have a rejiggering problem if you get a message like this:

(JobThread) java.lang.IncompatibleClassChangeError: Class org.nlogo.prim._asm_procedureoverlapping_report_0 does not implement the requested interface org.nlogo.prim.etc.InRadiusReporter

Rejiggering relies on classes having a report/perform, a report_1/perform_1, a report_2/perform_2, and so on. If there's nothing more than the basic report/perform, no rejiggering occurs, and the error listed above should never occur. Ideally, you could get your code to work with rejiggering, hopefully by avoiding the use of particular Scala features that seem to upset the thing, but, if you can't, there is a precedent in headless—a precedent set by Seth—to just say, "Well, this stuff is years old and the optimizations probably don't matter on a modern JVM, so just forget the rejiggering." Hopefully, your optimization is adding more in terms of performance than the rejiggering is.

After all that, go to compile/middle/optimize/package.scala, create an optimization object like all the other ones you see there. In your munge definition, start by storing each of the primitive's arugments into a variable via root.matchArg. You don't want to try to match them after you've started messing with the tree, because you might get some unexpected results—or at least I did. Then, do root.strip(), root.replace(classOf[<your new optimized prim>]), and graft any arguments into place. Simple. Next, open nvm/Optimizations.scala and add the name of your optimization where appropriate.

Next up, add some tests for your optimized code in OptimizerTests. If you don't know what exactly your test expects as a result, just do something like assertResult("")(compileReporter(<your code>)), run the test, check the failure message for the actual result you got, and use that (if the result looks right).

After that, make sure that your checksums are up-to-date. It's very possible that introducing some optimization will case a checksum test to pass, so you can generate the latest checksums by running ./sbt netlogo/allChecksums (you may also need to run ./sbt headless/allChecksums, or run netlogo/allChecksums in threed mode as well).

Afterwards, only one thing should still stand in your way: the benchdump tests. These tests make sure that models are being converted to the correct byte code. However, the definition of "correct" likely changed in several places when you added your optimized primitive. On account of that, run the benchdump tests and correct the stored benchdumps (found in test/benchdumps/) as necessary. If you find a benchdump to be predictably broken by your changes, run dump bench in SBT to update the benchdump.

At long last, you should (hopefully) have a working, optimized primitive that's well-tested and passes integration testing. Godspeed!

Clone this wiki locally