{"id":71,"date":"2013-05-26T03:30:00","date_gmt":"2013-05-26T03:30:00","guid":{"rendered":"http:\/\/evolvedmicrobe.com\/blogs\/?p=71"},"modified":"2014-05-11T13:12:38","modified_gmt":"2014-05-11T13:12:38","slug":"java-vs-c-performance-comparison-for-parsing-vcf-files","status":"publish","type":"post","link":"http:\/\/evolvedmicrobe.com\/blogs\/?p=71","title":{"rendered":"Java vs. C# Performance Comparison for Parsing VCF Files"},"content":{"rendered":"Making a comparison with a reasonably complex program ported between the two languages.\r\n<br\/><p\/>\r\n<p><em>Update 3\/10\/2014: After writing this post I changed the C# parser to remove an extra List&lt;&gt; allocation in the C# code that was not in the Java code.\u00a0\u00a0After this, the Java\/C# versions are indistinguishable on speed, but the C# code used ~88 MB of memory while the java version used &gt;1GB.\u00a0 Therefore, I now believe the winner is C# and a fast implementation of this parser (which can be over an order of magnitude faster for certain scenarios not in this test) is available\u00a0<a href=\"http:\/\/github.com\/evolvedmicrobe\/Bio.VCF\/tree\/master\">here<\/a><\/em><\/p>\r\n<p\/>\r\n<a href=\"http:\/\/www.1000genomes.org\/wiki\/Analysis\/Variant%20Call%20Format\/vcf-variant-call-format-version-41\" target=\"_blank\">VCF files<\/a> are a popular way to store information about genotypic variation from next generation sequencing studies.\u00a0 The files are essentially large matrices, where each element in the matrix represents a collection of information about the genotype of a particular person at a particular locus in the genome (in this sense, they can be considered as a multi-dimensional matrix in a flat file format).\r\n\r\nThe Java <a href=\"http:\/\/picard.sourceforge.net\/\" target=\"_blank\">Picard package<\/a> is a common utility used for parsing these files.\u00a0 While parsing, the challenge is to read each line (row) of the file, and construct objects for each element in that row that can then be manipulated or inspected.\u00a0 I just finished translating the Java VCF parser in Picard to C#, and so thought it might be a good chance to compare the two different languages and runtimes.\r\n\r\nC# showed a number of advantages in the translation.\u00a0 The translation itself was mostly a lot of deleting.\u00a0 The get\/set assessors in C# really allowed for the removal of seemingly endless amount of getXXXX\/setXXX methods in Java.\u00a0 It also seemed like every other line in Java was a call to some apache commons class to perform a simple task like get the maximum value in an array, create an empty list, or do a selection on data.\u00a0 Extension methods and Linq have clear advantages for data processing here (though I have found these have a slight overhead relative to the for loop equivalents).\u00a0 Yield statements in Java also would seem to be useful.\r\n\r\nAt the same time, Java had some things that would have been nice in C#.\u00a0 I had to implement basic collection types like immutable hashsets and dictionaries, a LinkedHashSet class as well as an OrderedGenericDictionary during the port.\u00a0 These should be in the C# language.\r\n<h1>Performance<\/h1>\r\n<strong><\/strong>This of course am what I am most interested in.\u00a0 My main computer is broken, so I had to test on my windows desktop at home.\u00a0 For the test, each program would read a gzipped VCF file for 20,000 lines, first creating an initial lazy class representing a portion of the data and then fully decoding this lazy version to represent the complete data as objects.\u00a0 The test file was a VCF with &gt;1,000 individuals, though unfortunately most of these individuals were non-calls at various positions, but its what I had on hand.\r\n\r\n<strong>Immediately after porting \u2013 <\/strong>After essentially re-writing the Java in C#, I ran some tests.\u00a0 Both Java and C# can run in either client (low-memory) or server modes.\u00a0 So I did both, here are the results:\r\n<table width=\"602\" border=\"3\" cellspacing=\"3\" cellpadding=\"2\">\r\n<tbody>\r\n<tr>\r\n<td valign=\"top\" width=\"136\"><strong>Platform<\/strong><\/td>\r\n<td valign=\"top\" width=\"121\"><strong>VM Options<\/strong><\/td>\r\n<td valign=\"top\" width=\"114\"><strong>Working Set<\/strong><\/td>\r\n<td valign=\"top\" width=\"106\"><strong>Paged Memory<\/strong><\/td>\r\n<td valign=\"top\" width=\"101\"><strong>Time (s)<\/strong><\/td>\r\n<\/tr>\r\n<tr>\r\n<td valign=\"top\" width=\"141\">Java<\/td>\r\n<td valign=\"top\" width=\"126\">None<\/td>\r\n<td valign=\"top\" width=\"119\">27.8 MB<\/td>\r\n<td valign=\"top\" width=\"111\">41.32 MB<\/td>\r\n<td valign=\"top\" width=\"103\">11.5<\/td>\r\n<\/tr>\r\n<tr>\r\n<td valign=\"top\" width=\"141\">.NET<\/td>\r\n<td valign=\"top\" width=\"127\">None<\/td>\r\n<td valign=\"top\" width=\"121\">28.9 MB<\/td>\r\n<td valign=\"top\" width=\"114\">30.22 MB<\/td>\r\n<td valign=\"top\" width=\"103\">15.1<\/td>\r\n<\/tr>\r\n<tr>\r\n<td valign=\"top\" width=\"139\">Ratio<\/td>\r\n<td valign=\"top\" width=\"127\"><\/td>\r\n<td valign=\"top\" width=\"122\">1<\/td>\r\n<td valign=\"top\" width=\"116\">1.35<\/td>\r\n<td valign=\"top\" width=\"103\">0.76<\/td>\r\n<\/tr>\r\n<tr>\r\n<td valign=\"top\" width=\"139\">Java<\/td>\r\n<td valign=\"top\" width=\"126\">Server<\/td>\r\n<td valign=\"top\" width=\"122\">362.2 MB<\/td>\r\n<td valign=\"top\" width=\"117\">414 MB<\/td>\r\n<td valign=\"top\" width=\"103\">7.4<\/td>\r\n<\/tr>\r\n<tr>\r\n<td valign=\"top\" width=\"139\">.NET<\/td>\r\n<td valign=\"top\" width=\"126\">Server (GC)<\/td>\r\n<td valign=\"top\" width=\"122\">126 MB<\/td>\r\n<td valign=\"top\" width=\"118\">332 MB<\/td>\r\n<td valign=\"top\" width=\"103\">14.7<\/td>\r\n<\/tr>\r\n<tr>\r\n<td valign=\"top\" width=\"138\">Ratio<\/td>\r\n<td valign=\"top\" width=\"126\"><\/td>\r\n<td valign=\"top\" width=\"122\">2.9<\/td>\r\n<td valign=\"top\" width=\"118\">1.25<\/td>\r\n<td valign=\"top\" width=\"103\">0.5<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\nA couple noticeable conclusions here. First Java is smoking .NET on performance, but this is essentially Java code at this point and it wasn\u2019t written for C#.\u00a0 Second, there is a massive amount more memory used in server mode, and in Java at least one obtains a large performance win for this cost.\r\n\r\n<strong>After \u201cSharpening\u201d the code \u2013 <\/strong>The initial port was basically Java, so I cleaned it up a bit after running it through the profiler, here are some notable changes:\r\n<ul>\r\n\t<li>String.Split winds up being a big cost in this parsing.\u00a0 When porting I initially just recreated an array every time, after I realized I was recreating such large arrays I reused them as in the Java code.<\/li>\r\n\t<li>In C# you can use unsafe pointers and I got a big performance win out of these.\u00a0 I grabbed the System.String.Split code from the Mono framework, trimmed\/altered it, and used it for all the split methods that seemed to be taking a long time.\u00a0 The Java version also implements a custom Split method, though obviously can\u2019t use pointers.<\/li>\r\n\t<li>Some additional cleanup in the logic.<\/li>\r\n<\/ul>\r\n<table width=\"602\" border=\"3\" cellspacing=\"3\" cellpadding=\"2\">\r\n<tbody>\r\n<tr>\r\n<td valign=\"top\" width=\"136\"><strong>Platform<\/strong><\/td>\r\n<td valign=\"top\" width=\"121\"><strong>VM Options<\/strong><\/td>\r\n<td valign=\"top\" width=\"114\"><strong>Working Set<\/strong><\/td>\r\n<td valign=\"top\" width=\"106\"><strong>Paged Memory<\/strong><\/td>\r\n<td valign=\"top\" width=\"101\"><strong>Time (s)<\/strong><\/td>\r\n<\/tr>\r\n<tr>\r\n<td valign=\"top\" width=\"141\">Java<\/td>\r\n<td valign=\"top\" width=\"126\">None<\/td>\r\n<td valign=\"top\" width=\"119\">27.8 MB<\/td>\r\n<td valign=\"top\" width=\"111\">41.32 MB<\/td>\r\n<td valign=\"top\" width=\"103\">11.6<\/td>\r\n<\/tr>\r\n<tr>\r\n<td valign=\"top\" width=\"141\">.NET<\/td>\r\n<td valign=\"top\" width=\"127\">None<\/td>\r\n<td valign=\"top\" width=\"121\">28.9 MB<\/td>\r\n<td valign=\"top\" width=\"114\">30.22 MB<\/td>\r\n<td valign=\"top\" width=\"103\">12.6<\/td>\r\n<\/tr>\r\n<tr>\r\n<td valign=\"top\" width=\"139\">Ratio<\/td>\r\n<td valign=\"top\" width=\"127\"><\/td>\r\n<td valign=\"top\" width=\"122\">1<\/td>\r\n<td valign=\"top\" width=\"116\">1.35<\/td>\r\n<td valign=\"top\" width=\"103\">0.92<\/td>\r\n<\/tr>\r\n<tr>\r\n<td valign=\"top\" width=\"139\">Java<\/td>\r\n<td valign=\"top\" width=\"126\">Server<\/td>\r\n<td valign=\"top\" width=\"122\">362.2 MB<\/td>\r\n<td valign=\"top\" width=\"117\">414 MB<\/td>\r\n<td valign=\"top\" width=\"103\">7.4<\/td>\r\n<\/tr>\r\n<tr>\r\n<td valign=\"top\" width=\"139\">.NET<\/td>\r\n<td valign=\"top\" width=\"126\">Server (GC)<\/td>\r\n<td valign=\"top\" width=\"122\">123 MB<\/td>\r\n<td valign=\"top\" width=\"118\">181.61 MB<\/td>\r\n<td valign=\"top\" width=\"103\">10.8<\/td>\r\n<\/tr>\r\n<tr>\r\n<td valign=\"top\" width=\"138\">Ratio<\/td>\r\n<td valign=\"top\" width=\"126\"><\/td>\r\n<td valign=\"top\" width=\"122\">3<\/td>\r\n<td valign=\"top\" width=\"118\">2.27<\/td>\r\n<td valign=\"top\" width=\"103\">0.68<\/td>\r\n<\/tr>\r\n<\/tbody>\r\n<\/table>\r\nSo round 2, and once again Java is the winner for speed in server mode, though at a high cost in memory.\u00a0 For the lower memory client model, it is nearly a tie between the two.\r\n<h2>What explains the difference?<\/h2>\r\nThese programs are nearly identical but the bottleneck is not the same in both. It seems <strong>C# can split strings much faster<\/strong> and <strong>Java can allocate memory much faster<\/strong>.\u00a0 Although I am less familiar with the Java profiler, it seems to show 66% of the time is spent on the String splits.\u00a0 In contrast, in C# the methods that are taking up time have to do with allocating memory (constructors) and the GC, string splits are only ~17% of the total time.\r\n\r\n<a href=\"https:\/\/i0.wp.com\/evolvedmicrobe.com\/blogs\/wp-content\/uploads\/2013\/05\/image.png\"><img loading=\"lazy\" style=\"background-image: none; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-width: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/i0.wp.com\/evolvedmicrobe.com\/blogs\/wp-content\/uploads\/2013\/05\/image_thumb.png?resize=625%2C193\" width=\"625\" height=\"193\" border=\"0\" data-recalc-dims=\"1\" \/><\/a>\r\n\r\n<a href=\"https:\/\/i0.wp.com\/evolvedmicrobe.com\/blogs\/wp-content\/uploads\/2013\/05\/image1.png\"><img loading=\"lazy\" style=\"background-image: none; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-width: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/i0.wp.com\/evolvedmicrobe.com\/blogs\/wp-content\/uploads\/2013\/05\/image_thumb1.png?resize=625%2C276\" width=\"625\" height=\"276\" border=\"0\" data-recalc-dims=\"1\" \/><\/a>\r\n\r\nOne the one hand, this means that the JVM is really doing a great job in server mode on memory allocations and other optimizations.\u00a0 I can\u2019t think why C# shouldn\u2019t be able to match the JVM performance (perhaps dynamic recompilation is really killing it here).\u00a0 On the other hand, it means that the C# program can still be improved, while I can\u2019t really see how to improve the Java one.\u00a0 The String.Split method has already been rewritten, and I didn\u2019t see any reasonable improvements for it.\u00a0 In contrast, both programs have several places where memory allocations can be saved.\u00a0 For example, one aspect of the parser is that it relies on a factory class to create another class and so allocates twice the memory that it needs as it creates two large objects.\u00a0 Simply having one object be it\u2019s own factory would solve this.\u00a0 Similarly, several empty lists are repeatedly created that could point to the same list or simply be skipped.\u00a0 I wanted this parser to generally match the Java version, so did not pursue these changes, but my guess is they may shrink the difference (though again, in Java this clearly didn\u2019t matter).\r\n<h1>Conclusions<\/h1>\r\nThe JVM is the clear winner on speed, particularly in server mode where memory isn\u2019t an issue.\u00a0 C# was the clear winner on brevity, syntax and language features.\u00a0 The difference was only substantial in server mode, and the C# program (and likely the Java program) were far from optimal, but it gives a rough hint at how they compare.\u00a0 The next question will be how they compare when C# runs with mono on a Linux environment.","protected":false},"excerpt":{"rendered":"Making a comparison with a reasonably complex program ported between the two languages. Update 3\/10\/2014: After writing this post I changed the C# parser to remove an extra List&lt;&gt; allocation in the C# code that was not in the Java code.\u00a0\u00a0After this, the Java\/C# versions are indistinguishable on speed, but the C# code used ~88 [&hellip;]","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true},"categories":[2,8,3,13],"tags":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":188,"url":"http:\/\/evolvedmicrobe.com\/blogs\/?p=188","url_meta":{"origin":71,"position":0},"title":"The .NET Bio BAM Parser is Smoking Fast","date":"October 12, 2013","format":false,"excerpt":"The .NET Bio library has an improved version of it's BAM file\u00a0parser, which makes it significantly faster and easily competitive with the\u00a0current standard C coded SAMTools for obtaining\u00a0sequencing data and working with it. The chart below compares the time it\u00a0takes in seconds for the old version of the parser and\u2026","rel":"","context":"In &quot;.NET Bio&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/evolvedmicrobe.com\/blogs\/wp-content\/uploads\/2013\/10\/img5.gif?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":299,"url":"http:\/\/evolvedmicrobe.com\/blogs\/?p=299","url_meta":{"origin":71,"position":1},"title":"C# vs. Java, Xamarin vs. Oracle, Performance Comparison version 2.0","date":"June 14, 2014","format":false,"excerpt":"Today I noticed the SIMD implementation of the Mandelbrot set algorithm I blogged about last year was successfully submitted to the language shootout webpage. However, I was a bit disappointed to see the C# version was still slower than the Java version, despite my use of the special SIMD instructions\u2026","rel":"","context":"Similar post","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":112,"url":"http:\/\/evolvedmicrobe.com\/blogs\/?p=112","url_meta":{"origin":71,"position":2},"title":"Mono.Simd and the Mandlebrot Set.","date":"September 10, 2013","format":false,"excerpt":"C# and .NET are some of the fastest high level languages, but still cannot truly compete with C\/C++ for low level speed, and C# code can be anywhere from 20%-300% slower. This is despite the fact that the C# compiler often gets as much information about a method as the\u2026","rel":"","context":"In &quot;Algorithms&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/evolvedmicrobe.com\/blogs\/wp-content\/uploads\/2013\/09\/img2_thumb.gif?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":398,"url":"http:\/\/evolvedmicrobe.com\/blogs\/?p=398","url_meta":{"origin":71,"position":3},"title":".NET Bio is Significantly Faster on .Net Core 2.0","date":"November 5, 2017","format":false,"excerpt":"Summary: With the release of .NET Core 2.0, .NET Bio is able to run significantly faster (~2X) on Mac OSX due to better compilation and memory mangement. The .NET Bio\u00a0library contains libraries for genomic data processing tasks like parsing, alignment, etc. that are too computationally intense to be\u00a0undertaken with interpreted\u2026","rel":"","context":"In \".NET Bio\"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/evolvedmicrobe.com\/blogs\/wp-content\/uploads\/2017\/11\/Benchmark-1.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":6,"url":"http:\/\/evolvedmicrobe.com\/blogs\/?p=6","url_meta":{"origin":71,"position":4},"title":"Not All Poisson Random Variables Are Created Equally","date":"January 30, 2013","format":false,"excerpt":"Spurred by a slow running program, I spent an afternoon researching what algorithms are available for generating Poisson random variables and figuring out which methods are used by R, Matlab, NumPy, the GNU Science Libraray and various other available packages. I learned some things that I think would be useful\u2026","rel":"","context":"In &quot;Algorithms&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/evolvedmicrobe.com\/blogs\/wp-content\/uploads\/2013\/01\/img34-300x239.jpg?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":91,"url":"http:\/\/evolvedmicrobe.com\/blogs\/?p=91","url_meta":{"origin":71,"position":5},"title":"Accessing dbSNP with C# and the .NET Platform","date":"August 22, 2013","format":false,"excerpt":"NCBI Entrez can be accessed with many different platforms (python, R, etc.) , but I find .NET one of the best because the static typing makes it easy to infer what all the datafields mean, and navigate the data with much greater ease. Documentation is sparse for this task, but\u2026","rel":"","context":"In &quot;Bioinformatics&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"http:\/\/evolvedmicrobe.com\/blogs\/index.php?rest_route=\/wp\/v2\/posts\/71"}],"collection":[{"href":"http:\/\/evolvedmicrobe.com\/blogs\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/evolvedmicrobe.com\/blogs\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/evolvedmicrobe.com\/blogs\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/evolvedmicrobe.com\/blogs\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=71"}],"version-history":[{"count":8,"href":"http:\/\/evolvedmicrobe.com\/blogs\/index.php?rest_route=\/wp\/v2\/posts\/71\/revisions"}],"predecessor-version":[{"id":251,"href":"http:\/\/evolvedmicrobe.com\/blogs\/index.php?rest_route=\/wp\/v2\/posts\/71\/revisions\/251"}],"wp:attachment":[{"href":"http:\/\/evolvedmicrobe.com\/blogs\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=71"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/evolvedmicrobe.com\/blogs\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=71"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/evolvedmicrobe.com\/blogs\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=71"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}