{"action":"created","issue":{"url":"https://api.github.com/repos/google/guava/issues/2112","repository_url":"https://api.github.com/repos/google/guava","labels_url":"https://api.github.com/repos/google/guava/issues/2112/labels{/name}","comments_url":"https://api.github.com/repos/google/guava/issues/2112/comments","events_url":"https://api.github.com/repos/google/guava/issues/2112/events","html_url":"https://github.com/google/guava/pull/2112","id":95911553,"number":2112,"title":"Use thread-local buffers to avoid memory allocation and improve performance","user":{"login":"berndhopp","id":1919315,"avatar_url":"https://avatars1.githubusercontent.com/u/1919315?v=4","gravatar_id":"","url":"https://api.github.com/users/berndhopp","html_url":"https://github.com/berndhopp","followers_url":"https://api.github.com/users/berndhopp/followers","following_url":"https://api.github.com/users/berndhopp/following{/other_user}","gists_url":"https://api.github.com/users/berndhopp/gists{/gist_id}","starred_url":"https://api.github.com/users/berndhopp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/berndhopp/subscriptions","organizations_url":"https://api.github.com/users/berndhopp/orgs","repos_url":"https://api.github.com/users/berndhopp/repos","events_url":"https://api.github.com/users/berndhopp/events{/privacy}","received_events_url":"https://api.github.com/users/berndhopp/received_events","type":"User","site_admin":false},"labels":[{"id":147571624,"url":"https://api.github.com/repos/google/guava/labels/cla:%20yes","name":"cla: yes","color":"009800","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":22,"created_at":"2015-07-19T12:23:19Z","updated_at":"2018-03-20T03:25:50Z","closed_at":null,"author_association":"NONE","pull_request":{"url":"https://api.github.com/repos/google/guava/pulls/2112","html_url":"https://github.com/google/guava/pull/2112","diff_url":"https://github.com/google/guava/pull/2112.diff","patch_url":"https://github.com/google/guava/pull/2112.patch"},"body":"# Abstract\n\ngoal of this PR is to replace\n\n```\nbyte[] buf = new byte[BUF_SIZE];\nByteArrayOutputStream baos = new ByteArrayOutputStream();\n```\n\nwith\n\n```\nBuffers.Lease<byte[]> lease = Buffers.leaseByteArray();\n\ntry{\n   byte[] buffer = lease.getBuffer();\n} finally {\n    lease.returnLease();\n}\n\nBuffers.Lease<ByteArrayOutputStream> lease = Buffers.leaseByteArrayOutputStream();\n\ntry{\n   ByteArrayOutputStream baos = lease.getBuffer();\n} finally {\n    lease.returnLease();\n}\n\n\n```\n\nin all cases where byte-array or ByteArrayOutputStreams are used method-internal, this way avoiding unneccessary memory allocation, increasing performance and enhancing the memory profile. \n# Implementation\n\n[ThreadLocalBuffers](https://github.com/berndhopp/guava/blob/master/guava/src/com/google/common/io/\nBuffers.java) is the manager of thread-local byte-arrays and FastByteArrayOutputStreams. It uses [SoftReferences](https://docs.oracle.com/javase/6/docs/api/java/lang/ref/SoftReference.html) or [WeakReferences](https://docs.oracle.com/javase/6/docs/api/java/lang/ref/WeakReference.html) to store buffers, this way enabling Garbage Collection to collect the buffers when the system runs low on memory. All allocations of byte[] or ByteArrayOutputStream in where the allocated byte[]/BAOS is not the returned value of the method have been replaced with calls to ThreadLocalBuffers. \nByteSource.contentEquals was challenging, because it used not one but two byte-arrays for buffering, so I used two ByteBuffers that wrap around the upper and lower part of the thread-local buffer, thereby mimicking the two buffers.\n# Results\n\nThe microbenchmarks show that avoiding buffer allocation yields a significant performance boost. Tests that did not show significant differences are left out. There have been no tests where performance dropped more than 2.00%. \n\n| tested method | bytes copied | runtime w/ TL buffers | runtime w/out TL buffers | performance increase in % |\n| --- | --- | --- | --- | --- |\n| ByteStreams.copy | 10 | 105569 | 681130 | 545,2 |\n| ByteStreams.copy | 100 | 107411 | 698152 | 549,98 |\n| ByteStreams.copy | 10000 | 840354 | 1186307 | 41,17 |\n| ByteStreams.readBytes | 10 | 68917 | 633365 | 819,03 |\n| ByteStreams.readBytes | 100 | 69802 | 660336 | 846,01 |\n| ByteStreams.readBytes | 10000 | 360357 | 934588 | 159,35 |\n| ByteStreams.toByteArray | 10 | 122681 | 750095 | 511,42 |\n| ByteStreams.toByteArray | 100 | 131705 | 773561 | 487,34 |\n| ByteStreams.toByteArray | 10000 | 1962961 | 4962929 | 152,83 |\n| ByteSource.contentEquals | 10 | 647140 | 3614650 | 458,56 |\n| ByteSource.contentEquals | 100 | 685559 | 647694 | 453,81 |\n| ByteSource.contentEquals | 10000 | 5987317 | 9460941 | 58,02 |\n\nyou can find the results here:\n\n[ByteStreamsBenchmark w/ ThreadLocal](https://microbenchmarks.appspot.com/runs/087a68fc-af92-4971-97ae-346be17ee50b#r:scenario.benchmarkSpec.methodName,scenario.benchmarkSpec.parameters.n)\n[ByteStreamsBenchmark w/out ThreadLocal](https://microbenchmarks.appspot.com/runs/dfb1c1b7-b9e2-431c-91f9-3be2f19db2a7#r:scenario.benchmarkSpec.methodName,scenario.benchmarkSpec.parameters.n)\n[ByteSourceBenchmark w ThreadLocal](https://microbenchmarks.appspot.com/runs/f5517421-8f23-4237-9de1-f7d9e4c8f3ce#r:scenario.benchmarkSpec.parameters.n)\n[ByteSourceBenchmark w/out ThreadLocal](https://microbenchmarks.appspot.com/runs/573ac1ff-a1e9-4870-9b0d-414e77e163e6#r:scenario.benchmarkSpec.parameters.n)\n"},"comment":{"url":"https://api.github.com/repos/google/guava/issues/comments/374460876","html_url":"https://github.com/google/guava/pull/2112#issuecomment-374460876","issue_url":"https://api.github.com/repos/google/guava/issues/2112","id":374460876,"user":{"login":"wikier","id":812223,"avatar_url":"https://avatars3.githubusercontent.com/u/812223?v=4","gravatar_id":"","url":"https://api.github.com/users/wikier","html_url":"https://github.com/wikier","followers_url":"https://api.github.com/users/wikier/followers","following_url":"https://api.github.com/users/wikier/following{/other_user}","gists_url":"https://api.github.com/users/wikier/gists{/gist_id}","starred_url":"https://api.github.com/users/wikier/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wikier/subscriptions","organizations_url":"https://api.github.com/users/wikier/orgs","repos_url":"https://api.github.com/users/wikier/repos","events_url":"https://api.github.com/users/wikier/events{/privacy}","received_events_url":"https://api.github.com/users/wikier/received_events","type":"User","site_admin":false},"created_at":"2018-03-20T03:25:50Z","updated_at":"2018-03-20T03:25:50Z","author_association":"NONE","body":"To learn from this PR and understand the background, while is still open? The feature request looks like a good idea to me. I'd need to dive deep in the implementation to provide further feedback. But what I still don't understand why is not closed (as `merge` or simply `closed`), at least not open forever."}}
{"actor":{"display_login":"wikier"}}