From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS4713 221.184.0.0/13 X-Spam-Status: No, score=-3.6 required=3.0 tests=AWL,BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, SPF_HELO_NONE,SPF_PASS shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from neon.ruby-lang.org (neon.ruby-lang.org [221.186.184.75]) by dcvr.yhbt.net (Postfix) with ESMTP id 76B4B1F461 for ; Thu, 29 Aug 2019 07:37:40 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 1AD1A120C27; Thu, 29 Aug 2019 16:37:30 +0900 (JST) Received: from o1678916x28.outbound-mail.sendgrid.net (o1678916x28.outbound-mail.sendgrid.net [167.89.16.28]) by neon.ruby-lang.org (Postfix) with ESMTPS id CF299120BC9 for ; Thu, 29 Aug 2019 16:37:27 +0900 (JST) Received: by filter0104p3las1.sendgrid.net with SMTP id filter0104p3las1-1094-5D6780B7-1B 2019-08-29 07:37:27.407953161 +0000 UTC m=+212794.847075510 Received: from herokuapp.com (unknown [54.227.36.202]) by ismtpd0037p1iad2.sendgrid.net (SG) with ESMTP id KAz8QT23SCWKZUTPp5M0Iw for ; Thu, 29 Aug 2019 07:37:27.219 +0000 (UTC) Date: Thu, 29 Aug 2019 07:37:27 +0000 (UTC) From: ko1@atdot.net Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 70210 X-Redmine-Project: ruby-trunk X-Redmine-Issue-Id: 16103 X-Redmine-Issue-Author: maciej.mensfeld X-Redmine-Sender: ko1 X-Mailer: Redmine X-Redmine-Host: bugs.ruby-lang.org X-Redmine-Site: Ruby Issue Tracking System X-Auto-Response-Suppress: All Auto-Submitted: auto-generated X-SG-EID: =?us-ascii?Q?fVTMYOBjtdvXNcWwrscBhLsHItUXVK5L4mtnq0mdcRcZMnfy8GXIvoPPKFGy=2F0?= =?us-ascii?Q?3e=2Fu=2FAWv7XSXiRM19mEJjuzVsdlbDRU+Iu9HE6Q?= =?us-ascii?Q?d7YJG0mu4Y5M4HNAEXJdanTGJHpg8=2Ftlq+y+OlN?= =?us-ascii?Q?uJPJwq7=2F4xwgh1Wae6fU+pOCY=2FunoSjjSYL1OL+?= =?us-ascii?Q?XiZIr2KC33TyW?= To: ruby-core@ruby-lang.org X-ML-Name: ruby-core X-Mail-Count: 94658 Subject: [ruby-core:94658] [Ruby master Feature#16103] Make the dot-colon method reference frozen X-BeenThere: ruby-core@ruby-lang.org X-Mailman-Version: 2.1.15 Precedence: list Reply-To: Ruby developers List-Id: Ruby developers List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ruby-core-bounces@ruby-lang.org Sender: "ruby-core" Issue #16103 has been updated by ko1 (Koichi Sasada). Matz accepted it :tada: ---------------------------------------- Feature #16103: Make the dot-colon method reference frozen https://bugs.ruby-lang.org/issues/16103#change-81260 * Author: maciej.mensfeld (Maciej Mensfeld) * Status: Open * Priority: Normal * Assignee: * Target version: ---------------------------------------- I made a PR to freeze the dot-colon method reference result object (https://github.com/ruby/ruby/pull/2267). Nobu asked to make an issue out of that. I initially discussed that with Matz and Ko1 during the hack challenge in Bristol. Here are some of the reasons why I think it should be done before releasing this feature: - It's worth keeping the bound methods frozen as one should not modify them in general - Freezing keeps a window of opportunity for introducing method reference caching in case it would be needed because the method object is immutable. - I want to work on the "last method" reference cache for that feature when we see more use-cases after 2.7 is released and without having it frozen, the cost and complexity are probably higher than the outcome. Example of the misuse that makes GC life much harder (note, that freezing does not fix that, but allows further work related to this issue): ```ruby # frozen_string_literal: true GC.disable class Parse def self.call(string) string.to_f end end class Normalize def self.call(number) number.round end end class Transform def self.call(int) int * 2 end end # Simulate a long running data producing source with batch results stream = Array.new(10_000) { Array.new(100) { '100.2' } } before = GC.stat[:total_allocated_objects] # This will provide batches to be processed, let's assume an endless data-source stream.each do |batch| batch .map(&Parse.:call) .map(&Normalize.:call) .map(&Transform.:call) end after = GC.stat[:total_allocated_objects] p a = after - before # 150001 before = GC.stat[:total_allocated_objects] # "cache" parse = Parse.:call normalize = Normalize.:call transform = Transform.:call stream.each do |batch| batch .map(&parse) .map(&normalize) .map(&transform) end after = GC.stat[:total_allocated_objects] p b = after - before # 120004 p "Difference: #{a - b}" # "Difference: 29997" ``` Things get even more "problematic" when referencing like above is not used on batches but on each of the objects separately: ```ruby # frozen_string_literal: true GC.disable class Parse def self.call(string) string.to_f end end class Normalize def self.call(number) number.round end end class Transform def self.call(int) int * 2 end end # Simulate a long running data producing source with batch results stream = Array.new(10_000) { Array.new(100) { '100.2' } } before = GC.stat[:total_allocated_objects] # This will provide batches to be processed, let's assume an endless data-source stream.each do |batch| batch.map do |message| message .then(&Parse.:call) .then(&Normalize.:call) .then(&Transform.:call) end end after = GC.stat[:total_allocated_objects] p a = after - before # 12010002 before = GC.stat[:total_allocated_objects] # This will provide batches to be processed, let's assume an endless data-source parse = Parse.:call normalize = Normalize.:call transform = Transform.:call stream.each do |batch| batch .map(&parse) .map(&normalize) .map(&transform) end after = GC.stat[:total_allocated_objects] p b = after - before # 120004 p "Difference: #{a - b}" # "Difference: 11889998" ``` I am aware, that this won't help in case we don't reuse the same object method references multiple times but based on many use-cases from here https://bugs.ruby-lang.org/issues/13581 and my own when building data-intense applications, it's not uncommon to build "pipelines" of things applied to each of the batches as a set of functions. Apart from that I would also vote on freezing the `.method(:method)` result object as well, but I know Matz was against. -- https://bugs.ruby-lang.org/