<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="5241" data-post-id="5241">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="bbense" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bbense/120/1074_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  bbense
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>The problem with most of these benchmarks is that what they actually do with the data is relatively trivial. Since most other languages are more or less using exactly the same I/O flow these benchmarks are reasonable tests of the relative speed of the text processing in each language.</p>
<p>In the case of Elixir and the BEAM, the I/O subsystem is so different that it’s costs dominate the equation and you end up comparing apples to oranges. There are a lot of tricks you can do to make the BEAM more orange-y, but ultimately, Elixir and the BEAM is not the optimal solution for every problem.</p>
<p>The BEAM makes a lot of very complicated things quite easy, but there is no free lunch. The price you pay for easy concurrency is complexity at the boundaries.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="5241" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/help-with-performance-file-io/802/22">Post #21</a>
	                </div>
	            </div>
              <div id="likers-container-5241" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="5241"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #21"></div>
  </section>
</div>
    <div class="postbit" id="5523" data-post-id="5523">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="karolsluszniak" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/karolsluszniak/120/1627_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  karolsluszniak
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>This topic got me interested both as an exercise with basic parts of Elixir, but also because I love writing shell scripts and I’m sure I’m not the only one considering Elixir as a nice language not just for servers, but tooling too. Pattern matching, easy packaging with escript, self-supervisioned daemons - the list goes on and on <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
<p>So I’ve also spent some time researching and optimizing and as result I wrote the following blog post:</p>
<p><a href="http://cloudless.pl/articles/12-elixir-vs-ruby-file-i-o-performance" rel="noopener nofollow ugc">Elixir vs Ruby: File I/O performance</a></p>
<p>There’s also <a href="https://github.com/karolsluszniak/process-csv" rel="noopener nofollow ugc">accompanying source code</a> available for everyone to play with.</p>
<p>Anyway, let me comment on some of your suggestions after trying them out:</p>
<blockquote>
<p><a class="mention" href="/u/nobbz" rel="nofollow">@NobbZ</a>: I do not think, that for that short script the compiletime will make any significant difference.<br>
<a class="mention" href="/u/andre1sk" rel="nofollow">@andre1sk</a>: build executable with escript?</p>
</blockquote>
<p>Both building for prod and/or escript decrease exec time by no more than 0.1s - 0.3s.</p>
<blockquote>
<p><a class="mention" href="/u/andre1sk" rel="nofollow">@andre1sk</a>: if you use IO.binstream instead of File.stream it speeds things up x2</p>
</blockquote>
<p>I couldn’t replicate that. In my case File.stream is always faster than File.open + IO.stream or binstream. Could you check that out on <a href="https://github.com/karolsluszniak/process-csv" rel="noopener nofollow ugc">my code</a>?</p>
<blockquote>
<p><a class="mention" href="/u/cjbottaro" rel="nofollow">@cjbottaro</a>: Anyway, I have a lot more to say about it (and more Elixir performance questions), but that’s for another post… <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
</blockquote>
<p>I’d really like to read more about your findings. Especially about how did you manage to overcome the file I/O performance bottleneck, as I guess you had to settle with some final solution for your distributed computation framework.</p>
<blockquote>
<p><a class="mention" href="/u/bbense" rel="nofollow">@bbense</a> Getting fast I/O stream processing in Elixir does require some non-obvious tweaks. Due to the way I/O works on the BEAM (there’s a special process that does I/O and passes results to your process as a message), you want the messages to be as long as possible to avoid overheads.</p>
</blockquote>
<p>This turned out to be very true in my benchmarks. The question is how to efficiently parse large files that need to be processed on per-line basis and are too big to read them whole, but without streaming them line by line which is so slow in Elixir.</p>
<p>I’ve implemented a simple concurrent solution where each process loaded specific part of file and did manual binary line splitting and joining of broken lines at part breaks but it was slower than simple <code>File.stream</code> (as it only increased the number of messages).</p>
<blockquote>
<p><a class="mention" href="/u/bbense" rel="nofollow">@bbense</a> Hmm, using separate process to read separate sections of the file is one trick I have not tried.</p>
</blockquote>
<p>I have (with <code>:file.pread</code> and <code>Task.async</code>). It didn’t turn out well <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="5523" data-batch-url="/posts/batch_likers">
                        4
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/help-with-performance-file-io/802/23">Post #22</a>
	                </div>
	            </div>
              <div id="likers-container-5523" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="5523"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #22"></div>
  </section>
</div>
    <div class="postbit" id="5537" data-post-id="5537">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="sasajuric" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/sasajuric/120/991_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  sasajuric
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Author of Elixir In Action</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="karolsluszniak" data-post="23" data-topic="802">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/karolsluszniak/48/1627_2.png" class="avatar"> karolsluszniak:</div>
<blockquote>
<p>So I’ve also spent some time researching and optimizing and as result I wrote the following blog pos</p>
</blockquote>
</aside>
<p>There are many interesting points in that post. However, I disagree that the task is mostly I/O bound. In fact, it seems to me the task is mostly CPU bound, spending most of its time in <code>filter_line</code> (in the “read” version). I fiddled with it a bit, and was able to reduce the read version to about 1sec (from 1.8) with the following approach:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defp filter_line(line) do
  filter_line(line,&lt;&lt;&gt;&gt;)
end

defp filter_line(&lt;&lt;c::utf8, rest::binary&gt;&gt;, integer) when c in ?0..?9 do
  filter_line(rest, &lt;&lt;integer::binary, c&gt;&gt;)
end
defp filter_line(&lt;&lt;?,::utf8, _::binary&gt;&gt;, integer) do
  num = String.to_integer(integer)
  rem(num, 2) == 0 || rem(num, 5) == 0
end
defp filter_line(&lt;&lt;_other::utf8, rest::binary&gt;&gt;, _integer) do
  filter_line(rest, &lt;&lt;&gt;&gt;)
end
</code></pre>
<p>Basically, I’m iterating one codepoint at a time, accumulating integer codepoints (0..9) until the first observed comma. Then I convert the accumulated string of digits to int, and do the rem math. By doing this I don’t need to split the entire line by comma (since I don’t care about the rest anyway), and also don’t need to do additional regex scan for the terminating integer.</p>
<p>I didn’t do much to analyze the correctness besides verifying that the output file size is the same. I’m also not saying this is the best (or optimal) approach. The point is, as I hinted <a href="http://forum.elixirforum.com/t/help-with-performance-file-io/802/15" rel="nofollow">earlier in this thread</a>, that most of the action is taking place in a few CPU bound functions (assuming you perform I/O efficiently, which you do with <code>File.read!</code> and <code>File.write!</code>). If Ruby’s versions of those operations are implemented in C, then Ruby will probably be faster.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="5537" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/help-with-performance-file-io/802/24">Post #23</a>
	                </div>
	            </div>
              <div id="likers-container-5537" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="5537"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #23"></div>
  </section>
</div>
    <div class="postbit" id="5538" data-post-id="5538">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="karolsluszniak" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/karolsluszniak/120/1627_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  karolsluszniak
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Thanks for the feedback, that’s exactly what I was going after when I wrote that article <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"> I’m not that good with binary pattern matching yet so it’s nice to see it put into good use.</p>
<p>You’re right that the read version spends lots of time running string and regex operations in the <code>filter_line</code> function. I’ve actually left it unoptimized (and as close to Ruby equivalent as possible) on purpose in order to focus on the File I/O itself. You could also reduce the <code>rem(num, 2) == 0 || rem(num, 5) == 0</code> to a check if last char is one of <code>0 2 4 5 6 8</code> <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
<p>Instead, I’d like to treat that part as constant and try making things faster by changing the I/O flow itself. Especially in the streaming version which could really use some performance boost and in which no optimizations in <code>filter_line</code> will help much. For serious use with serious (or arbitrary) file sizes, streaming may be the only way to go.</p>
<p>As to being CPU and not I/O bound, do you think it’s also true for the streaming version? If it’s the message sending/receiving between processes that kills that version’s performance, then I thought it’s correct to say we’re I/O bound (as it’s the I/O layer that puts this unavoidable strain on the VM). On the other hand message passing probably involves CPU more than hard disk or RAM so from that perspective we’re CPU bound in that version too. What do you think?</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="5538" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/help-with-performance-file-io/802/25">Post #24</a>
	                </div>
	            </div>
              <div id="likers-container-5538" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="5538"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #24"></div>
  </section>
</div>
    <div class="postbit" id="5539" data-post-id="5539">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="sasajuric" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/sasajuric/120/991_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  sasajuric
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Author of Elixir In Action</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="karolsluszniak" data-post="25" data-topic="802">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/karolsluszniak/48/1627_2.png" class="avatar"> karolsluszniak:</div>
<blockquote>
<p>You could also reduce the rem(num, 2) == 0 || rem(num, 5) == 0 to a check if last char is one of 0 2 4 5 6 8 <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
</blockquote>
</aside>
<p>Ah, this is a great trick! I just tried it, and it reduces the running time to 0.8 sec:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defp filter_line(&lt;&lt;c::utf8, ?,::utf8, _::binary&gt;&gt;)
  when c in [?0, ?2, ?4, ?5, ?6, ?8],
  do: true
defp filter_line(&lt;&lt;_::utf8, ?,::utf8, _::binary&gt;&gt;),
  do: false
defp filter_line(&lt;&lt;_other::utf8, rest::binary&gt;&gt;),
  do: filter_line(rest)
</code></pre>
<aside class="quote no-group" data-username="karolsluszniak" data-post="25" data-topic="802">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/karolsluszniak/48/1627_2.png" class="avatar"> karolsluszniak:</div>
<blockquote>
<p>I’ve actually left it unoptimized (and as close to Ruby equivalent as possible) on purpose in order to focus on the File I/O itself.</p>
</blockquote>
</aside>
<p>I don’t see why does it have to look as close to ruby. Isn’t the point of this exercise to do the work as fast as possible?</p>
<aside class="quote no-group" data-username="karolsluszniak" data-post="25" data-topic="802">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/karolsluszniak/48/1627_2.png" class="avatar"> karolsluszniak:</div>
<blockquote>
<p>For serious use with serious (or arbitrary) file sizes, streaming may be the only way to go.</p>
</blockquote>
</aside>
<p>If you want to limit the memory usage, then yes. To make it work faster, you can use a read-ahead buffer. Similarly, you could use delayed write on the writing side. Here’s a version which takes less than 2 secs on my machine (with the optimized <code>filter_line</code>):</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">def main([ filename, "stream" ]) do
  File.stream!(filename, read_ahead: 100_000)
  |&gt; Stream.filter(&amp;filter_line/1)
  |&gt; Stream.into(File.stream!(filename &lt;&gt; ".out", [:delayed_write]))
  |&gt; Stream.run
end
</code></pre>
<p>It’s not as efficient as the eager one, but much faster than the original. Perhaps it can be further optimized, but I didn’t spend any time investigating it.</p>
<aside class="quote no-group" data-username="karolsluszniak" data-post="25" data-topic="802">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/karolsluszniak/48/1627_2.png" class="avatar"> karolsluszniak:</div>
<blockquote>
<p>As to being CPU and not I/O bound, do you think it’s also true for the streaming version?</p>
</blockquote>
</aside>
<p>Well, we have to read a lot of data, and write a lot of data, so we’re partially I/O bound in both versions. In the streaming one we’re doing I/O less efficiently (but reducing the memory usage), so that’s a trade-off.</p>
<aside class="quote no-group" data-username="karolsluszniak" data-post="25" data-topic="802">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/karolsluszniak/48/1627_2.png" class="avatar"> karolsluszniak:</div>
<blockquote>
<p>As to being CPU and not I/O bound, do you think it’s also true for the streaming version? If it’s the message sending/receiving between processes that kills that version’s performance, then I thought it’s correct to say we’re I/O bound</p>
</blockquote>
</aside>
<p>I consider I/O bound to mean we’re doing I/O operations, i.e. talking to external devices. Message passing (unless to a different machine) is not such operation so it’s not I/O bound in my opinion. Regardless, message passing overhead might be significant if the processes are doing little work on each message.</p>
<p>In any case I’d say it’s first worth optimizing the sequential algorithm (perhaps by manually recursing, and making the decision with less processing <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20">) before considering splitting the work over multiple processes. Concurrency is not a remedy for a suboptimal sequential algorithm <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="5539" data-batch-url="/posts/batch_likers">
                        3
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/help-with-performance-file-io/802/26">Post #25</a>
	                </div>
	            </div>
              <div id="likers-container-5539" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="5539"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #25"></div>
  </section>
</div>
    <div class="postbit" id="5542" data-post-id="5542">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="karolsluszniak" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/karolsluszniak/120/1627_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  karolsluszniak
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<blockquote>
<p>If you want to limit the memory usage, then yes. To make it work faster, you can use a read-ahead buffer. Similarly, you could use delayed write on the writing side.</p>
</blockquote>
<p>Wow, the <code>:delayed_write</code> indeed is a game changer here! I’ve somehow missed it in the docs. I’ve already tried  <code>:read_ahead</code> and it didn’t affect performance by much. This still holds true when used in tandem with <code>:delayed_write</code> although now the benefit of increasing <code>:read_ahead</code> seems to be a bit bigger.</p>
<blockquote>
<p>I don’t see why does it have to look as close to ruby. Isn’t the point of this exercise to do the work as fast as possible?</p>
</blockquote>
<p>You’re actually right. I was thinking to just keep that part of the script away from reflections on my blog post, as kind of a constant that doesn’t change between scripts. But it’s not fair to Elixir not to use its pattern matching and recursive powers, esp. since I compare the total performance anyway.</p>
<p>Thanks again! I’ll update the blog post with those findings, optimizations and with a corrected statements about being I/O bound.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="5542" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/help-with-performance-file-io/802/27">Post #26</a>
	                </div>
	            </div>
              <div id="likers-container-5542" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="5542"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #26"></div>
  </section>
</div>
    <div class="postbit" id="5556" data-post-id="5556">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="sasajuric" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/sasajuric/120/991_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  sasajuric
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Author of Elixir In Action</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="karolsluszniak" data-post="27" data-topic="802">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/karolsluszniak/48/1627_2.png" class="avatar"> karolsluszniak:</div>
<blockquote>
<p>You’re actually right. I was thinking to just keep that part of the script away from reflections on my blog post, as kind of a constant that doesn’t change between scripts. But it’s not fair to Elixir not to use its pattern matching and recursive powers, esp. since I compare the total performance anyway.</p>
</blockquote>
</aside>
<p>Yeah, the point was to optimize the code algorithmically. Having established that most of the time is spent in the code which decides whether the line should be filtered or not, I tried to reduce the processing there.  We don’t need to process the entire line if we’re deciding on the first column only. This had some savings in your csv example, but could have saved a lot more for larger rows. Your trick with checking the last digit is also a great save, since we don’t have to collect all the digits, nor convert to int.</p>
<p>So now, we’re better than Ruby, although it would be interesting to see how Ruby would perform with similar optimizations.</p>
<p>Moreover, the <code>:delayed_write</code> puts the streaming version in the area of Ruby (which I suspect buffers by default). So my non-conclusive conclusion would be that for this contrived microbenchmark we can expect similar performance in both languages (assuming both implementations are tuned), so I don’t see problems with Elixir I/O compared to Ruby (which your otherwise great post seems to imply).</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="5556" data-batch-url="/posts/batch_likers">
                        3
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/help-with-performance-file-io/802/28">Post #27</a>
	                </div>
	            </div>
              <div id="likers-container-5556" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="5556"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #27"></div>
  </section>
</div>
    <div class="postbit" id="5599" data-post-id="5599">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="karolsluszniak" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/karolsluszniak/120/1627_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  karolsluszniak
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Just wanted to give you (and everyone here) a heads up that a heavily rewritten article on Elixir file I/O is up:</p>
<p><a href="http://cloudless.pl/articles/12-elixir-vs-ruby-file-i-o-performance-updated" rel="noopener nofollow ugc">Elixir vs Ruby: File I/O performance (updated)</a></p>
<p>I think it’s worth it to quote here the key conclusion on files and Elixir:</p>
<blockquote>
<p>In case of Elixir you can get similar performance if you put streams into proper use (as shown above) or if you go for a read-all-at-once approach. You can also gain a serious performance edge over Ruby if you make use of pattern matching and recursion.</p>
</blockquote>
<blockquote>
<p>Therefore, it makes most sense to write such scripts from scratch with precise idea about how to put unique Elixir features into use. I can see some serious use cases here that could take benefit from OTP, pattern matching and streaming, like supervisioned CSV import/export workers, Unix daemons or command line tools. Doing blind conversion, like I did in this experiment, makes little sense and doesn’t yield a fair comparison.</p>
</blockquote>
<p>Aside from rewritten conclusions, it also includes a much more logical layout of the whole optimization process. And gives credit where the credit is due <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"> I hope this time I’ve nailed it and it’ll serve a proper reference for everyone who stumbles upon this problem.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="5599" data-batch-url="/posts/batch_likers">
                        8
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/help-with-performance-file-io/802/29">Post #28</a>
	                </div>
	            </div>
              <div id="likers-container-5599" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="5599"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-most-liked cat-most-liked" title="One of the top 3 liked posts in this thread!"></div>
  </section>
</div>
    <div class="postbit" id="5619" data-post-id="5619">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="uranther" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/uranther/120/2627_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  uranther
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Thank you for this write-up! It clearly explains the various ways to slice this problem, explores Elixir/OTP strengths and weaknesses, while also bringing in the Ruby perspective. <img src="https://forum.elixirforum.com/images/emoji/apple/heavy_check_mark.png?v=15" title=":heavy_check_mark:" class="emoji" alt=":heavy_check_mark:" loading="lazy" width="20" height="20"><img src="https://forum.elixirforum.com/images/emoji/apple/bookmark.png?v=15" title=":bookmark:" class="emoji" alt=":bookmark:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="5619" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/help-with-performance-file-io/802/30">Post #29</a>
	                </div>
	            </div>
              <div id="likers-container-5619" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="5619"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #29"></div>
  </section>
</div>
    <div class="postbit" id="5626" data-post-id="5626">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="sasajuric" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/sasajuric/120/991_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  sasajuric
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Author of Elixir In Action</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>This looks great!</p>
<p>I’m puzzled about one sentence though:</p>
<blockquote>
<p>I was wrong assuming that it’s the process communication that puts the biggest overhead here.</p>
</blockquote>
<p>Just to be clear: there’s no process communication happening here. Everything happens in the same process, since you’re working with both files in the <code>raw</code> mode (default for file streams). For clarification, see “Processes and raw files” in <a href="http://elixir-lang.org/docs/stable/elixir/File.html" rel="nofollow">File doc</a>.</p>
<p>Also, I couldn’t sleep over the fact that streamed version was about 3x slower on my machine than the read version (1.8s vs 0.6s). I played with it a bit more and discovered that streaming bytes works much faster than streaming lines. That led me to the following solution which shaved down the streaming version to ~ 1s:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">def main([ filename, "stream" ]) do
  out_file = File.open!(filename &lt;&gt; ".out", [:write, :raw, :delayed_write])

  File.stream!(filename, [], 4000)
  |&gt; Enum.reduce({"", out_file}, &amp;handle_chunk/2)

  File.close(out_file)
end

defp handle_chunk(chunk, {unfinished_line, file}) do
  (unfinished_line &lt;&gt; chunk)
  |&gt; String.split("\n")
  |&gt; process_lines(file)
end

defp process_lines([unfinished_line], file), do: {unfinished_line, file}
defp process_lines([line | rest], file) do
    if filter_line(line) do
      IO.binwrite(file, line &lt;&gt; "\n")
    end
    process_lines(rest, file)
end
</code></pre>
<p>Here, I’m taking chunks of 4k (larger chunks didn’t improve perf). When I read the chunk I append it to the unfinished line from the previous chunk. Then I split on newline, and process all except the last element. The last element is unfinished line which I’ll prepend to the next chunk and repeat.</p>
<p>At this point, streaming version is also faster than Ruby (which is ~ 1.8s on my machine).</p>
<p>Note that I’m splitting input per bytes, so I’m not sure whether this will work correctly with unicode files.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="5626" data-batch-url="/posts/batch_likers">
                        3
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/help-with-performance-file-io/802/31">Post #30</a>
	                </div>
	            </div>
              <div id="likers-container-5626" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="5626"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #30"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <a class="load-more-button" data-turbo-stream="true" href="/topics/802/load_more?page=4">Load more posts (6 remaining)</a>
</div></template></turbo-stream>