{"id":13725339,"url":"https://github.com/neonsquare/bucklescript-benchmark","last_synced_at":"2025-05-07T20:32:03.853Z","repository":{"id":40002960,"uuid":"86379538","full_name":"neonsquare/bucklescript-benchmark","owner":"neonsquare","description":"A small benchmark comparing Bucklescript to plain JavaScript","archived":false,"fork":false,"pushed_at":"2017-03-30T11:10:08.000Z","size":32,"stargazers_count":41,"open_issues_count":2,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-08-04T01:27:40.445Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neonsquare.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-27T20:11:58.000Z","updated_at":"2024-05-20T11:09:54.000Z","dependencies_parsed_at":"2022-06-26T06:40:32.957Z","dependency_job_id":null,"html_url":"https://github.com/neonsquare/bucklescript-benchmark","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neonsquare%2Fbucklescript-benchmark","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neonsquare%2Fbucklescript-benchmark/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neonsquare%2Fbucklescript-benchmark/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neonsquare%2Fbucklescript-benchmark/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neonsquare","download_url":"https://codeload.github.com/neonsquare/bucklescript-benchmark/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224645341,"owners_count":17346128,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T01:02:19.973Z","updated_at":"2024-11-14T15:31:13.102Z","avatar_url":"https://github.com/neonsquare.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# BuckleScript Benchmark\n\nThis little benchmark demonstrates how static compilation using a\ncompiler like\n[Bucklescript](https://github.com/bloomberg/bucklescript) can make\nvery similar looking [Reason](https://facebook.github.io/reason/) code run an order of magnitude faster than naively written JS.\n\n## Descriptions of the Code-Variants:\n\n### `src/friends.re`\nA straight forward implementation in Reason.\n\n### `src/friends.js`\nDirect adaption from the Reason code. Is using JS objects instead of a\nrecord. Immutable object update is done with `Object.assign` which is\nsimilar to the Object spread syntax that is often used when\ntranspiling JS with Babel. The array gets updated immutable through\nArray spread.\n\n## Unfair / cheating Variants\n\nThe following variants implement strategies in JS to make it run faster. In a way those solutions are \"cheating\", because they don't actually do the same thing. They break rules like immutability or maintainability of code. The purpose here is to show, what price you have to pay if you want similar terse code like the Reason variant but more speed than the first JS variant. Interestingly none of this variants is actually faster then the Reason variant on Node.js (V8)!\n\n### `src/friends_literal.js`\nInstead of `Object.assign` this variant uses a direct object literal\nand any property gets manually set from the source object. This is\nsignificantly faster then Object.assign because it sidesteps reading out which fields the object has and looping over them at runtime. But: One has to manually and carefully\nmaintain that any possible field in the source object gets set in\nthe clone. If you change your record structure you have to change this code. You at least could implement a `clonePerson` method to abstract this out - but it nevertheless is an additional step you have to do that is automaticalle taken care for you in Reason.\n\n### `src/ffriends.js`\nThis variant doesn't behave immutable. Instead the inital person\nobjects get modified directly and also the friends-Array is modified\nusing `Array.push()` instead of `Array.spread()`. So it clearly breaks one of the essential contracts of this benchmarks. Most interestingly it isn't even faster than the Reason variant on Node.js (V8). If you do not even update the Array at all (just return the initial object unmodified!) - it is still much slower than Reason! Curious why? [Read here!](#where-is-the-price) \n\n### `lib/js/src/friends.js`\nWell... yeah... thats just the Bucklescript output. Of course it is exactly as fast as the Reason code because it actually is the result of it. Of course you could hand write such code, but it is much more difficult to do and maintain so. You lose the terseness and maintainability of the Reason code and also any further improvements you could get by using future Bucklescript versions.\n\n## Build and Run\n\nTo build the javascript code from the Reason-Code using Bucklescript just run `npm run build`.\n\nTo run the benchmark run `npm start`. The output should be similar to the following:\n\n```\nTimings:\n\nReason: using BuckleScript Records/Lists : 710.390ms\nJS:     using Object.assign              : 8263.039ms\n\nTimings of unfair/cheating variants\nJS: using Object and manual key mapping (brittle code!)         : 3123.591ms\nJS: using Object mutation (no immutability!)                    : 1721.166ms\n```\n(Measured using Node.js v7.7.1 on a MacBook Pro Retina 2,3 GHz Intel\nCore i7, 16GB RAM)\n\nOf course the timings depend on the used JS engine!\n\n## How realistic is such code?\n\nFunctional programming patterns are more and more common in modern Javascript projects. Strictly controlling side effects is an important aspect when applying them. Directly modifying data structures makes it harder to follow the state flow of complex programs. So instead of this, a functional program makes use of immutable datastructures.\n\nJavaScript objects are inherently optimized to get directly modified at runtime. Though, operations like `Object.assign` or Syntax extensions like Object spread or Array spread make it very easy to code in a style that implements immutable datastructures ad hoc from plain javascript objects and arrays. Typical use cases are state transformations implemented in Redux reducers. The example used in this benchmark uses this style to demonstrate how this can lead to bad performance compared to code that is compiled using a compiler that knows that its datastructures actually really are immutable and that the field offsets are known by compile time. This means there is very cheap field lookup and cloning a whole record doesn't need to access individual keys like in JavaScript.\n\nThe second JavaScript  variant demonstrates, that even a direct destructive modification of the Array within the person records themselves may not necessarily give better performance. It also isn't generating exactly the same result, because the order of the friends array ends up reversed.\n\n**This leads to an interesting outcome: While languages like [TypeScript](http://www.typescriptlang.org)\ncan use their static type system to offer better tooling or less\nruntime errors, they still are - by their whole definition -  bound to\nthe mutative nature of JavaScript**\n\nA language like [Reason](https://facebook.github.io/reason/) with a\ncompiler like BuckleScript has a much stricter definition. Those\nconstraints open up the door to more freedom in optimizing code and\nchoosing more efficient data structure representations.\n\n## Hand optimization vs. Compilers\n\nSome readers may note, that this example doesn't show that\n\"Bucklescript\" generates faster code than \"handwritten\" JS because one could just\nmanually write code that does the same as what Bucklescript generates.\nThis is true, but it isn't practical. While this may work for very small\ntoy examples like this benchmark, it never would work for real\nprograms. It is nearly impossible to sustain this kind of hand\noptimization across public API boundaries.\n\nThe main point here is, that a language like Reason defines simple\nlanguage semantics of OCaml that _abstract away_ any concrete representation of\ncode and values so that they can later be changed, improved or\noptimized. Automatically. Or short: A future improved BuckleScript compiler\ncan generate even faster code out of the same given Reason example. Without\nchanging the code itself.\n\n## Where is the price?\n\n[Bucklescript](https://github.com/bloomberg/bucklescript) doesn't just map records directly on JavaScript objects. It instead  uses a representation of Arrays and any property access is compiled to simple array index accesses. This representation is the reason how the result can deliver such a good runtime performance. Also lists are not mapped 1:1 on Arrays! Their representation uses 2-element arrays where the first element is the list head and the 2nd element is the tail of the list:\n\n```\n[1,2,3,4]\n-\u003e\n[1, [2, [3, [4, 0]]]]\n```\n\nThis makes pushing a new element onto a list a constant timed operation (just allocate a new \"CONS-Pair\" with the new head item and the old list as tail).\n\nOn the other side: If records and lists are structured this way, they\nare more difficult to handle in arbitrary JS code or within a\ndebugging session. You lose the easy runtime introspectability of\nplain javascript objects. Blunt said: you do not directly see the\nnames of the fields anymore. If you want to access such record\ninstances from JavaScript you need to write accessor functions in\nBuckleScript.\n\nIf you want to use idiomatic JS objects in [Bucklescript](https://github.com/bloomberg/bucklescript) (e.g. for\ninteroperability with JS libs) - you can use the FFI (Foreign Function\nInterface) of BuckleScript. This FFI is what makes a big difference to\nlanguages like TypeScript: When writing pure BuckleScript\n(OCaml/Reason) code, the compiler is free to take any representation\nit likes, as all interoperability is restricted _within_ the Reason/OCaml language\nsemantics. With the FFI one interfaces the pure written code with the\nJavaScript runtime. This is where BuckleScript has to play by the \nrules of its host language and accept the dynamic behaviour there.\n\n--\nJochen H. Schmidt\n27. Mar. 2017\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneonsquare%2Fbucklescript-benchmark","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneonsquare%2Fbucklescript-benchmark","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneonsquare%2Fbucklescript-benchmark/lists"}