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


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Ok .. I think I have found the issue …</p>
<p>In every call to get a line, the process returned by StringIO.open does this:</p>
<pre><code>defp io_request({:get_line, encoding, prompt}, s) do
    get_line(encoding, prompt, s)
end
</code></pre>
<p>get_line, in turn call Erlang’s :unicode.characters_to_list. This converts the whole bitstring to a list with the proper encoding.</p>
<p>If this succeeds, StringIO.do_get_line is called which iterates over the items in the list until it finds a termination (newline or no more data) and returns that line and the rest of the string. It then goes back to Erlang calling :unicode.characters_to_binary on both the line just retrieved and the remainder of the string.</p>
<p>Which means that the longer the string the bigger the lists and then resulting binaries that are being generated will be on <em>each iteration</em> requesting a line. I expect this is doing some unhappy things to the memory management. A potential fix would be to do the conversion to a list once and keep that in the state data of the StringIO process and then iterate over that one line at a time.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="32327" 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/emulate-file-stream-for-a-string-variable/4983/12">Post #11</a>
	                </div>
	            </div>
              <div id="likers-container-32327" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="32327"
                     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 #11"></div>
  </section>
</div>
    <div class="postbit" id="32328" data-post-id="32328">
  <section>
    <div class="post-wrap">


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>p.s. I do see why it was probably written this way… there is no guarantee at all that after one call to get_line, another will follow .. it’s just as valid for the next call to be to get one char or 5 chars or whatever … since it can not assume it will just all be a bunch of get_line calls, StringIO is doing its best to return the string to a state where whatever the next call happens to be, it will work.</p>
<p>Still, probably should be keeping some bookkeeping information in its state so it knows where it can pick up from ..</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="32328" data-batch-url="/posts/batch_likers">
                        0
                      </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/emulate-file-stream-for-a-string-variable/4983/13">Post #12</a>
	                </div>
	            </div>
              <div id="likers-container-32328" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="32328"
                     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 #12"></div>
  </section>
</div>
    <div class="postbit" id="32329" data-post-id="32329">
  <section>
    <div class="post-wrap">


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Issue is already known to the elixir team</p>
<aside class="onebox githubissue" data-onebox-src="https://github.com/elixir-lang/elixir/issues/6037">
  <header class="source">

      <a href="https://github.com/elixir-lang/elixir/issues/6037" target="_blank" rel="noopener nofollow">github.com/elixir-lang/elixir</a>
  </header>

  <article class="onebox-body">
    <div class="github-row">
  <div class="github-icon-container" title="Issue" data-github-private-repo="false">
	  <svg width="60" height="60" class="github-icon" viewBox="0 0 14 16" aria-hidden="true"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg>
  </div>

  <div class="github-info-container">
    <h4>
      <a href="https://github.com/elixir-lang/elixir/issues/6037" target="_blank" rel="noopener nofollow">Performance problem reading from StringIO</a>
    </h4>

    <div class="github-info">
      <div class="date">
        opened <span class="discourse-local-date" data-format="ll" data-date="2017-04-26" data-time="13:17:50" data-timezone="UTC">01:17PM - 26 Apr 17 UTC</span>
      </div>

        <div class="date">
          closed <span class="discourse-local-date" data-format="ll" data-date="2017-05-16" data-time="14:42:10" data-timezone="UTC">02:42PM - 16 May 17 UTC</span>
        </div>

      <div class="user">
        <a href="https://github.com/balexand" target="_blank" rel="noopener nofollow">
          <img alt="" src="https://avatars.githubusercontent.com/u/181187?v=4" class="onebox-avatar-inline" width="20" height="20">
          balexand
        </a>
      </div>
    </div>

    <div class="labels">
        <span style="display:inline-block;margin-top:2px;background-color: #B8B8B8;padding: 2px;border-radius: 4px;color: #fff;margin-left: 3px;">
          App:Elixir (stdlib)
        </span>
    </div>
  </div>
</div>

  <div class="github-row">
    <p class="github-body-container">### Environment

Mac OS 10.12.4 on an older Macbook Pro

```
&gt; elixir --ver<span class="show-more-container"><a href="" rel="noopener nofollow" class="show-more">…</a></span><span class="excerpt hidden">sion
Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Elixir 1.4.2
```

### Current behavior

Reading from a `StringIO` using `IO.read(pid, :line)` is extremely slow. In the benchmark below with 30,000 lines it takes 2,500 times longer to read from a `StringIO` than to read from a file.

### Expected behavior

I would expect reading from a `StringIO` to have similar performance to reading from a file. Furthermore, I would expect to read N lines in O(N) time.

### Benchmark

```elixir
defmodule Read do
  def with_timing(label, pid) do
    {micro_sec, :ok} = :timer.tc(fn -&gt; until_eof(pid) end)
    IO.puts "#{label}: #{round(micro_sec / 1_000)}ms"
  end

  def until_eof(pid) do
    case IO.read(pid, :line) do
      :eof -&gt; :ok
      line when is_binary(line) -&gt; until_eof(pid)
    end
  end
end

num_lines = 30_000
IO.puts "Number of lines: #{num_lines}"

lots_of_lines =
  0..num_lines
  |&gt; Enum.map(fn _ -&gt; "abcdefghijklmnopqrstuvwxyz" end)
  |&gt; Enum.join("\n")

{:ok, pid} = StringIO.open(lots_of_lines)

Read.with_timing("StringIO", pid)

# For comparison, read the same lines from file
path = "/tmp/lots_of_lines.txt"
File.write!(path, lots_of_lines, [:utf8])

file_pid = File.open!(path, [:read, :utf8])
Read.with_timing("File", file_pid)
```

### Results

```
➜ elixir perf.exs
Number of lines: 10000
StringIO: 56336ms
File: 94ms
➜ elixir perf.exs
Number of lines: 20000
StringIO: 291517ms
File: 172ms
➜ elixir perf.exs
Number of lines: 30000
StringIO: 616679ms
File: 247ms
```</span></p>
  </div>

  </article>

  <div class="onebox-metadata">
    
    
  </div>

  <div style="clear: both"></div>
</aside>

<p>also there is an PR addressing this issue</p>
<aside class="onebox githubpullrequest" data-onebox-src="https://github.com/elixir-lang/elixir/pull/6056">
  <header class="source">

      <a href="https://github.com/elixir-lang/elixir/pull/6056" target="_blank" rel="noopener nofollow">github.com/elixir-lang/elixir</a>
  </header>

  <article class="onebox-body">
    <div class="github-row" data-github-private-repo="false">



    <div class="github-icon-container" title="Pull Request">
      <svg width="60" height="60" class="github-icon" viewBox="0 0 12 16" aria-hidden="true"><path fill-rule="evenodd" d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>
    </div>

  <div class="github-info-container">



      <h4>
        <a href="https://github.com/elixir-lang/elixir/pull/6056" target="_blank" rel="noopener nofollow">StringIO read_line performance and binread bug fix (#6056)</a>
      </h4>

    <div class="branches">
      <code>master</code> ← <code>balexand:string_io</code>
    </div>

      <div class="github-info">
        <div class="date">
          opened <span class="discourse-local-date" data-format="ll" data-date="2017-05-01" data-time="11:13:55" data-timezone="UTC">11:13AM - 01 May 17 UTC</span>
        </div>

        <div class="user">
          <a href="https://github.com/balexand" target="_blank" rel="noopener nofollow">
            <img alt="" src="https://avatars.githubusercontent.com/u/181187?v=4" class="onebox-avatar-inline" width="20" height="20">
            balexand
          </a>
        </div>

        <div class="lines" title="9 commits changed 2 files with 50 additions and 36 deletions">
          <a href="https://github.com/elixir-lang/elixir/pull/6056/files" target="_blank" rel="noopener nofollow">
            <span class="added">+50</span>
            <span class="removed">-36</span>
          </a>
        </div>
      </div>
  </div>
</div>

  <div class="github-row">
    <p class="github-body-container">This pull request currently consists of 3 separate commits:

1. bf28be6243392a<span class="show-more-container"><a href="https://github.com/elixir-lang/elixir/pull/6056" target="_blank" rel="noopener nofollow" class="show-more">…</a></span><span class="excerpt hidden">5c40367d329fa7ed233e257a2b Adds test coverage to the existing implementation.
2. 2a475a6a8d1e80b1c8790915b0d08a3eb4357429 Fixes #6037, the performance/scalability problem.
3. c7e821394841c3eda7743f37933a60d501675125 Fixes a bug. `IO.binread` should return raw bytes instead of decoding the input as if it were Latin1. See [this comment](https://github.com/elixir-lang/elixir/issues/6037#issuecomment-298233279).

I can split the last item into a separate pull-request if desired.

Using [this benchmark](https://gist.github.com/balexand/629e37f97865ce404f002199c3b406cd), each `IO.read(pid, :line)` call takes:

```
Before: 2557µs when N=5,000 O(N)
After: 14µs O(1)
```

`StringIO.get_until` suffers from the same performance problem but I've avoided touching it (and `collect_line`) because it has almost no test coverage. When I delete the entire function the only test that fails is:

```
1) test with get until (ExUnit.CaptureIOTest)
   test/ex_unit/capture_io_test.exs:196
   Assertion with == failed
   code:  capture_io(fn -&gt; :io.scan_erl_form('&gt;') end) == "&gt;"
   left:  ""
   right: "&gt;"
   stacktrace:
     test/ex_unit/capture_io_test.exs:197: (test)
```

I'd be happy to work on `get_until` in a separate pull-request. But first, does `StringIO` even need to support `get_until` or can we delete it? Also, is there any documentation about `get_until` other than http://erlang.org/doc/apps/stdlib/io_protocol.html?</span></p>
  </div>

  </article>

  <div class="onebox-metadata">
    
    
  </div>

  <div style="clear: both"></div>
</aside>
 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="32329" data-batch-url="/posts/batch_likers">
                        0
                      </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/emulate-file-stream-for-a-string-variable/4983/14">Post #13</a>
	                </div>
	            </div>
              <div id="likers-container-32329" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="32329"
                     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 #13"></div>
  </section>
</div>
    <div class="postbit" id="32332" data-post-id="32332">
  <section>
    <div class="post-wrap">


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>That PR looks very nice … hopefully it gets merged soon! As usual, great to see the pace of devel around Elixir in action <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="32332" data-batch-url="/posts/batch_likers">
                        0
                      </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/emulate-file-stream-for-a-string-variable/4983/15">Post #14</a>
	                </div>
	            </div>
              <div id="likers-container-32332" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="32332"
                     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 #14"></div>
  </section>
</div>
    <div class="postbit" id="151525" data-post-id="151525">
  <section>
    <div class="post-wrap">


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Alternative solution to using <code>StringIO</code> + <code>IO.binstream</code> would be to use <code>Stream.unfold</code> with binary pattern matching:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">  def binary_stream(b, chunk_size \\ 50_000) when is_binary(b) do
    Stream.unfold(0, fn skip -&gt;
      case b do
        &lt;&lt;_skipped::binary-size(skip), chunk::binary-size(chunk_size), _rest::binary&gt;&gt; -&gt;
          {chunk, skip + chunk_size}

        &lt;&lt;_skipped::binary-size(skip)&gt;&gt; -&gt;
          nil

        &lt;&lt;_skipped::binary-size(skip), chunk::binary&gt;&gt; -&gt;
          {chunk, skip + byte_size(chunk)}
      end
    end)
  end
</code></pre>
<p>I tested this on pretty big XML docs (hundreds of megabytes) and it seems to be performant and doesn’t require much memory due to not copying/cloning any part of the binary data.</p>
<p>PS: for this particular case (XML parsing), it’s not necessary to read by line, and in fact some XML documents (or SOAP API responses) return whole XML doc as a single long line without line breaks.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="151525" data-batch-url="/posts/batch_likers">
                        10
                      </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/emulate-file-stream-for-a-string-variable/4983/16">Post #15</a>
	                </div>
	            </div>
              <div id="likers-container-151525" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="151525"
                     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 #15"></div>
  </section>
</div>
    <div class="postbit" id="162038" data-post-id="162038">
  <section>
    <div class="post-wrap">


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

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Thanks for this post, it was very usefull !</p>
<p>But I think the post marked as solution is not the more appropriate.<br>
The solution using <code>String.splitter(str, "\n")</code> seems to behave more as expected.</p>
<p>Explanations:<br>
When using this:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">str = "abc\ndef\nghi\n"

{:ok, stream} = str |&gt; StringIO.open()

s = stream |&gt; IO.binstream(:line)
</code></pre>
<p>it do not work as expected:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">iex&gt; s |&gt; Enum.take(1)
["abc\n"]
iex&gt; s |&gt; Enum.take(1)
["def\n"]
</code></pre>
<p>=&gt; The result should always be the same as it’s the same operation.</p>
<p>Like we have here:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">iex&gt; s = File.stream!("/tmp/foo.csv")
iex&gt; s |&gt; Enum.take(1)
["hey\n"]
iex&gt; s |&gt; Enum.take(1)
["hey\n"]
</code></pre>
<p>Instead, <code>String.splitter(str, "\n")</code>, do the job as expected:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">iex&gt; str = "abc\ndef\nghi\n"
iex&gt; s = String.splitter(str, "\n")

iex&gt; s |&gt; Enum.take(1)
["abc"]
iex&gt; s |&gt; Enum.take(1)
["abc"]
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="162038" 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/emulate-file-stream-for-a-string-variable/4983/17">Post #16</a>
	                </div>
	            </div>
              <div id="likers-container-162038" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="162038"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-last-post cat-last-post" title="Last post!"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <span class="all-loaded">— All posts loaded —</span>
</div></template></turbo-stream>