dataesr/scientific-paragraphs-categorization huggingface dataset:
In most cases, the matching process was straightforward (see Supplementary Figure  2A ), and each cluster coordinate set could be simply matched to the nearest coordinate set at the previous timepoint for colony growth rate calculation. Several issues, shown in Supplementary Figures  2B, C , nevertheless required further improvement of the script (see details in Supplementary Methods in Supplementary Material). The R code used for Hierarchical Cluster Analysis (HCA) of microcolony growth profiles can be found in the Supplementary files in Supplementary Material [see "hierarchical_cluster_analysis. zip" that includes the input data, the R code and an example of data reading and HCA on Ntd-Tsl1 growth rates (Replicate #1)]. For batch cultures in bioreactors, raw OD curves were first treated with time translation (x-axis) to homogenize the initial lag-time and make growth curves as concomitant as possible during their early exponential phase.
A scan of the slides was performed every 2 h for 8 h, to follow the growth of every single microcolony. The collected data give us access to several parameters as areas (µm 2 ) and XY coordinates on the slides. After data treatment [see (B)], the distribution of the growth rate of thousand microcolonies could be plotted and samples compared. (B) Data treatment to estimate growth rates. This relied on customized Python scripts that allowed: Colonies realignment, which provided a corrective to the shift of the XY coordinates, due to the manual repositioning of the glass slides under the microscope objective at each time point; Cells gathering to correct splitting of the microcolony; Growth rate calculation between time points, from log(area) values, for each microcolony. (C) For each time interval, the distribution of growth rates of thousands microcolonies was presented as density plots.
The raw microscopy datasets included the ID number, area, center X and Y coordinates for each object (i. e. , single cell or microcolony) at each time point. The ID numbers were different after each scan, so they could not be used to follow a specific object across time points. Also, the manual repositioning of the glass slides on the microscope at each time point led to a slight shift of the XY positions, with an average of few micrometers, which prevented direct use of this parameter for objects filiation across time points. Our strategy was therefore to merge the datasets from the different time points and use XY position plots to identify cell (or microcolonies) clusters as a function of time (Supplementary Figure  2A ). This colony realignment step hence relied on the development of customized Python scripts (see Supplementary Methods in Supplementary Material, analysis.
py script). Adjustments were also required for more complex situations found in some of the datasets. As is shown in Supplementary Figure  2B , the coordinates cannot easily be matched at first glance, and attempting to match them based on the simple method of minimal distance failed. For realignment, we evaluated a cost function for each time point based on how closely it overlays the coordinates of the first time point. We shifted each time point around in each direction and selected the linear adjustment that minimizes the cost function (see Supplementary Methods in Supplementary Material, analysis. py script). The second adjustment originated from another issue that can interfere with the matching strategy described above. Here, even if time points appeared to be grouped together in welldefined clusters, the problem came from some clusters that had more than one coordinate for a time point, mainly for time point 5 (Supplementary Figure  2C ).
We could attribute these extraevents to either rare, yet possible dissemination of new-born cells from growing colonies, or to artificial splitting of the signal during image acquisition by the microscope. In both cases, these events were very often separated by a small gap, i. e. , less than a micrometer. As these co-localized events might be considered as a single object arising from a single cell after spreading, the script was improved to gather corresponding signals into a single event, which leads to more accurate area estimation relative to the development of the single cell of interest (see Supplementary Methods in Supplementary Material, analysis. py script). We then used area (µm 2 ) measurements as a proxy of cell division and microcolony growth as mentioned in Levy et al and described in methods.
As is shown in the histogram presented in Supplementary Figure  3A  for one representative dataset, we noticed that using a cell density of 1,000 cells/mm 2 during slides preparation, 20% of cells presented 0 or 1 neighbor, while 17% presented five or more neighbors within this area. Interestingly, the good superposition of the cumulative plots of growth rate values for these two categories of cells (Supplementary Figures  3B-D ; three different strains taken as representative examples), demonstrated that the proximity of other microcolonies had no significant impact during the first divisions, which was confirmed by the non-parametric Wilcoxon tests used to compare these groups. As a conclusion, we could not associate non-or slow-growing cells to cells with a high number of neighbors after plating.
251 ± 0,022 h -1 , 0. 238 ± 0,027 h -1 and 0. 258 ± 0,023 h -1. Increasing the number of replicates did not convincingly reduce the SD that was highly dependent on the occurrence of outlier values, as illustrated by the highest SD of the study (0,027) that was obtained from five replicates of the Ntd-Tsl1 strain. The BY strain (three replicates) and Ntd-Tps1 (seven replicates) returned approximately the same SD (0,022 vs. 0,023). By using the mean and SD determined from these three datasets, the confidence intervals for the growth rates at 95% confidence level are respectively  (0. 196, 0. 306), (0. 204, 0. 271), and (0. 236, 0. 279). Even in the best situation, random realization of seven replicates for a strain of interest hence gave an estimation of the mean growth rate that is not really satisfying from the yeast physiologist view point (µ±0.
ACKNOWLEDGMENTS We are grateful to  Delphine Lestrade  and  Julien Cescut  from the  Toulouse White Biotechnology consortium  for flow cytometry facilities. We also thank  Luc Fillaudeau  for his helpful contribution on the Morphologi G3-S microscope,  Marion Schiavone  and  Childerick Severac  for contributing to the plasma treatment of the microchambered culture slides, and  Sébastien Déjean  for fitting growth curves from controlled bioreactors.
Ruby is a dynamically typed programming language with a large breadth of features which has grown in popularity with the rise of the modern web, and remains at the core of the implementation of many widely-used websites. CRuby, the default implementation of the language, features a JIT compiler known as MJIT, but developers often do not enable it in production environments, because it does not always yield performance improvements on real-world software. Attempts to independently reimplement the Ruby language, such as JRuby and TruffleRuby have shown impressive performance results on benchmarks, but often lag behind CRuby when it comes to supporting new additions to the language, which limits their adoption. We introduce YJIT, a new JIT compiler built inside CRuby based on a Lazy Basic Block Versioning (LBBV) architecture.
Introduction Ruby is a programming language featuring dynamic typing and garbage collection, as well as both object-oriented and functional programming constructs. The language, which takes inspiration from Perl and Smalltalk, has grown in popularity with the rise of the modern web, and remains at the core of the implementation of many widely-used websites. The feature-rich and highly dynamic nature of Ruby make it challenging to optimize. CRuby, the official Ruby implementation, includes a JIT compiler known as MJIT, but despite impressive numbers on synthetic benchmarks, MJIT has not, thus far, yielded speedups in production workloads. Large-scale enterprise users such as Shopify and GitHub typically leave it disabled, relying on the CRuby interpreter only. There are ongoing efforts, such as JRuby and TruffleRuby, to produce alternative Ruby implementations.
These have shown great peak performance numbers, but for a variety of reasons, both practical and political, real-world deployments are still very limited. The current situation mirrors that of alternative reimplementations of other existing languages. TruffleRuby, JRuby, PyPy and LuaJIT are all impressive implementation efforts with attractive performance numbers, but there is often a gap in terms of compatibility with and support for all the features of the latest reference implementation. This gap makes it challenging for users to switch. YJIT is an open source 1  development effort being led at Shopify, with active participation from developers at GitHub, to build a new JIT compiler for Ruby inside the reference CRuby implementation. Retrofitting a new JIT compiler into an older codebase which was not originally designed with JIT compilation in mind presents its own set of challenges, however, it also comes with some important benefits.
Namely, YJIT is nearly 100% compatible with the latest CRuby as well as all existing Ruby packages, and it is practically a drop-in replacement for existing CRuby binaries, making deployment very straightforward. As of today, YJIT has been able to pass all of the nearly 30,000 CRuby unit tests, and to match the reference CRuby implementation in all of the tests in the Shopify and GitHub continuous integration (CI) test suites. The architecture of YJIT is based on a Lazy Basic Block Versioning (LBBV) JIT compiler architecture  [6, 7]. This workin-progress paper provides clear evidence that such an architecture can realistically scale to large, real-world software, providing both fast warmup times and significant speedups. Our contributions are: • An open source implementation of YJIT, a JIT compiler based on LBBV for the Ruby programming language which is highly compatible with existing Ruby code.
• Empirical evidence that a JIT compiler based on an LBBV architecture can deliver fast warmup times on benchmarks based on real-world software. • Empirical evidence that YJIT produces speedups relative to the current CRuby interpreter and MJIT on benchmarks based on real-world software. Section 2 of this paper delves into some background about Ruby and its reference implementation. Section 3 surveys key details of the architecture and implementation of YJIT. Section 4 looks at performance results and warmup time on benchmarks. Finally, section 5 surveys related work and section 6 outlines directions for future work.
The CRuby Virtual Machine The core of CRuby is a stack-based interpreter, which executes YARV bytecode instructions  [17]  one at a time, with each instruction able to push and pop values off of the temporary stack. A second separate stack is used to keep track of control frame objects corresponding to activation records for method calls. We will mention specific aspects that are relevant to this work, but the design of the CRuby VM is explained in great detail in the book Ruby Under a Microscope  [19]. CRuby uses a system of tag bits to differentiate small immediate values such as fixnums (63-bit integers), as well as the constants true, false, and nil from heap objects, and a generational mark and sweep garbage collector which has the ability to move objects in memory. Ruby, taking inspiration from Smalltalk, makes heavy use of method calls.
MJIT MJIT compiles Ruby source by generating C source code. It calls an external C compiler, then loads the output via dynamic linking. Outputting C source code allows MJIT to reuse parts of the interpreter's source in its output where appropriate, allowing for ease of development. It uses a background thread to generate C code and waits for the external C compiler to produce a shared library. MJIT translates Ruby methods to C functions largely by concatenating the interpreter's implementation of bytecode instructions. Branch instructions are mapped to C control flow constructs and method call instructions have special handling. MJIT uses the same monomorphic inline cache structure as the interpreter to specialize call sites. For select callees that are leaves (do not make further calls), MJIT is able to avoid creating a stack frame.
Hitting a stub in turn interrupts execution to resume code generation. LBBV interleaves code generation and execution, which opens up opportunities to specialize code based on the types, values, and execution paths that are encountered at run-time. Code that is not executed is never compiled. Machine code is appended to an array, and stubs are generated in a separate out-of-line (not inline) memory region so that they do not impact the instruction cache when they are not executed. The resulting generated code tends to be fairly linear and follows the natural flow of execution seen at run-time, helping reduce the occurrence of branch mispredictions. To prevent a code size explosion due to the generation of multiple versions of basic blocks, YJIT relies on the same simple scheme described in  [6] , that is, we allow a fixed, maximum number of versions to be generated for each block (currently set to 4 by default).
Integration with CRuby YJIT uses CRuby's direct-threaded interpreter as a baseline. A counter monitors how many times a given method is called, and when a threshold is hit (currently 10 calls by default), YJIT begins incrementally generating code for said method. A JIT entry point is installed, which all future calls to the method will dispatch to. Generated code for methods with branches may include stubs that are only executed when a given target of a branch is hit. These stubs call back into YJIT (see Figure  1 ), which lazily generates more code on demand, patches the stub, and returns control to the newly generated code. The CRuby bytecode is rich and complex, and YJIT does not implement every operation on every type. Guards are used to specialize the fast path for various operations.
When guards fail, YJIT can exit back to the interpreter, even in the middle of a method. This is done by restoring the interpreter's stack pointer and program counter, and then returning control to the interpreter. During development, statistics are routinely gathered to establish which operations cause us to exit to the interpreter most frequently, which guides our development, with the goal of maximizing time spent in code we generate. For the sake of simplicity, YJIT uses the same calling convention and stack frame layout as CRuby, which makes entering and leaving generated code relatively simple. At this stage, YJIT also uses the CRuby stack to store temporary values, and has no register allocator. This is far from optimal, but it simplifies interfacing with the CRuby GC, and we believe that the main source of overhead, at present, is the inefficiency of the CRuby calling convention (see Section 6).
Run-Time Value Promotion We implemented a technique originally described by Armin Rigo and used in the Psyco VM for Python  [13] , then subsequently implemented in PyPy  [2]. That is, YJIT can promote run-time values to compile-time constants, implementing the equivalent of Psyco's unlift operator. This is done by interrupting compilation and inserting a new stub which will then peek at the run-time value, and resume compilation once the value is known. The use of LBBV makes this relatively simple because our architecture naturally allows incrementally compiling subsets of a method. YJIT relies on the promotion of run-time values to implement a mechanism that is functionally similar to polymorphic inline caches. When generating code for an instance variable read instruction, we peek at the run-time value and determine its class, so that we can generate specialized machine code for reading the requested property on the class that was encountered.
Evaluation A list of benchmarks used in our experiments is provided in Table  2 , along with the number of instruction sequences (iseqs) compiled by YJIT to give an idea of scale and complexity. Methods which are never called are never compiled. We have intentionally avoided using microbenchmarks in our evaluation, and favored benchmarks that are based on realworld software. Our largest benchmarks are liquid-render, which renders Shopify's Liquid web template language, activerecord, which is based on the widely-used database access middleware, and railsbench, using the Ruby on Rails web framework. The optcarrot benchmark is a synthetic benchmark that implements a Nintendo console emulator. We have included it because it is sizeable, and was considered an optimization target by the Ruby core developers in the development of CRuby for a long time.
The Lee benchmark was written by Chris Seaton, key contributor on the TruffleRuby project, to experiment with transactional memory. The 30k_methods and 30k_ifelse benchmarks are synthetic benchmarks written by us, designed to be a torture test for YJIT. These were not included for performance comparisons against other systems, but rather to assess whether large code size would lead to a collapse of performance due to instruction cache misses. Each benchmark features several thousand small methods that call each other in an acyclic call graph with depth 30. The generated code is large enough that it cannot completely fit in the L1 instruction cache of any modern CPU. The 30k_ifelse benchmark has conditional branches to make branch prediction more challenging. To increase the stability of our benchmark results, experiments were performed on AWS c5.
metal dedicated instances with CPU frequency scaling disabled. The AWS instances used were running Ubuntu Linux 20. 04 on Intel Xeon Cascade Lake processors. Each benchmark run was given 500 warmup iterations before 200 timing iterations were performed. Final results are averaged over 15 full runs for CRuby, YJIT, MJIT, and TruffleRuby. Experiments with CRuby's MJIT used version 3. 0. 2, while experiments on YJIT were based on CRuby version 3. 1. 0dev, YJIT commit hash e9990eda71 2. We used a publicly released version of CRuby to benchmark MJIT because there are often performance bugs in prerelease versions. For benchmarks Generating HTTP responses with a Rails application 1381 using MJIT, we specified the command-line arguments -jit-max-cache=10000 as well as --jit-min-calls=10 to enable it to reach the best peak performance possible.
3 For experiments with TruffleRuby, version 21. 2. 0 of the JVM implementation was used as recommended by members of the TruffleRuby team, with the --jvm command-line switch. The results in Figure  2  show a comparison of the mean execution time after warmup for the CRuby interpreter, YJIT, MJIT, and TruffleRuby. Importantly, YJIT is always faster than the interpreter, which is not the case for MJIT. Even on railsbench, our most complex benchmark, YJIT achieves a speedup of 14% over the interpreter. Its performance compares favorably to that of MJIT, particularly when looking at benchmarks such as liquid-render, activerecord, and railsbench, which are all based on real-world software.
Related Work Smalltalk & Self Key ideas in dynamic language optimization, such as the use of inline caches and maps, which are now more commonly referred to as object shapes, can be traced back to Smalltalk  [10]  and Self  [5]. Quickening is a technique for optimizing the performance of interpreters  [4]  by using inline caching to dispatch to specialized versions of bytecode instructions. This results in code which is easier for the compiler to optimize and has fewer unpredictable branches. YJIT leverages LBBV to achieve the same benefits and more, but quickening has the advantage that it is easier to implement and does not involve code generation. Run-time Value Promotion The Psyco prototype for Python  [13]  proposed the notion of specializing an interpreter based on the running program.
A key concept was the idea of promoting run-time values to compile-time so that code could be specialized based on these. This notion was later successfully adapted to JavaScript  [8]. YJIT also implements run-time value promotion and uses it to specialize instance variable accesses and method calls. PyPy and Meta-Tracing The PyPy project began as a followup to Psyco. PyPy  [2, 3]  uses a tracing JIT compiler to trace through a Python interpreter, itself written in an restricted subset of Python known as RPython  [1]. This interpreter contains a few special annotations which help the tracing JIT unroll it into compiled and optimized native code. Lazy Basic Block Versioning is a novel kind of JIT compiler architecture first introduced in  [6]  and subsequently extended to propagate type information interprocedurally in  [7].
The technique has been adapted for use in functional programming languages such as Scheme  [14, 15]  and to optimize gradual typing  [12]. JRuby is a JVM-based implementation of Ruby  [16, 23]  which is still in active development. It leverages JVM features aimed at optimizing dynamic languages such as invokedynamic and offers very competitive performance results on some benchmarks. However, warmup times can be lengthy on larger benchmarks, and it greatly lacks when it comes to compatibility with existing Ruby code, because it does not support native Ruby gems, which are very common. TruffleRuby is an implementation of Ruby based on the Truffle/Graal platform. The Truffle platform for building VMs bears some similarity to the idea of meta-tracing found in PyPy. An annotated interpreter is written for the source language, and this interpreter is automatically rewritten and compiled into optimized code using partial evaluation  [21, 22].
TruffleRuby began by reusing code shared with JRuby, and eventually became an independent Ruby implementation. It benefits from many years of work on The Truffle platform, but also integrates optimizations that are specific to Ruby  [9, 11, 18 ]. Stripe's Sorbet Compiler Stripe has recently announced that they will be open sourcing an ahead-of-time Ruby compiler 4 they have been working on. This compiler translates Ruby source files with additional Sorbet type annotations (gradual typing) into Ruby gems that integrate with the CRuby VM. Their motivations for building a compiler that integrates with CRuby include compatibility and ease of deployment, similarly to YJIT. Other Ruby implementations There has been a surprisingly large number of projects aiming to implement Ruby JIT compilers and VMs, though most of these are no longer being maintaned.
Future Work Faster Calling Convention Ruby is a programming language dominated by method calls. Basic constructs like loops to iterate over elements of an arrays are typically implemented as method calls with block arguments (closures) and most method bodies are very short. The way CRuby implements method calls is meant to be convenient for an interpreter, but it is clearly suboptimal for a JIT compiler. Each send instruction must set up a new control frame object that lives on a separate stack, and write multiple fields to it. We believe that the overhead from method calls currently dominates execution time. Inlining is possible in some cases, but in order to become performance-competitive, CRuby will need a faster calling convention, otherwise the current design will always impose an upper bound on the performance that can be achieved.