ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
From: merch-redmine@jeremyevans.net
To: ruby-core@ruby-lang.org
Subject: [ruby-core:99457] [Ruby master Feature#17055] Allow suppressing uninitialized instance variable and method redefined verbose mode warnings
Date: Mon, 03 Aug 2020 16:30:56 +0000 (UTC)	[thread overview]
Message-ID: <redmine.journal-86914.20200803163055.1604@ruby-lang.org> (raw)
In-Reply-To: redmine.issue-17055.20200728220329.1604@ruby-lang.org

Issue #17055 has been updated by jeremyevans0 (Jeremy Evans).

File t.rb added

Eregon (Benoit Daloze) wrote in #note-12:
> jeremyevans0 (Jeremy Evans) wrote in #note-11:
> > The last time I did testing on this in Sequel, the performance decrease from initializing instance variables to nil was around 5% for Sequel::Model instance creation depending on the plugins in use. One of the reasons it was around 5% was that many plugins had to override initialization methods just to set an instance variable and call `super`.  5% may not sound like a lot, but I can't justify a 5% performance decrease (or even a 1% performance decrease) just to avoid verbose warnings.
> 
> Could you reproduce that again?
> I would like a realistic benchmark, so unless users frequently create Sequel::Model instances themselves, I guess the normal case is the data comes from somewhere (the database, some file, etc).
> In such a case I would think the overhead is not measurable.

I'm glad you asked, as the difference is actually much greater than I remember it being. With the attached benchmark, on PostgreSQL localhost, the difference is over 100% when not using plugins:

```
$ NO_SEQUEL_PG=1 DATABASE_URL=postgres:///?user=sequel_test ruby t.rb regular noplugin
Warming up --------------------------------------
  Retrieve 1000 rows    31.000  i/100ms
Calculating -------------------------------------
  Retrieve 1000 rows    310.225  (_ 2.6%) i/s -      1.550k in   5.000031s

$ NO_SEQUEL_PG=1 DATABASE_URL=postgres:///?user=sequel_test ruby t.rb eager_initialize noplugin
Warming up --------------------------------------
  Retrieve 1000 rows    15.000  i/100ms
Calculating -------------------------------------
  Retrieve 1000 rows    151.327  (_ 2.0%) i/s -    765.000  in   5.057570s
```

and over 150% when using plugins:

```
$ NO_SEQUEL_PG=1 DATABASE_URL=postgres:///?user=sequel_test ruby t.rb regular plugin
1000
Warming up --------------------------------------
  Retrieve 1000 rows    23.000  i/100ms
Calculating -------------------------------------
  Retrieve 1000 rows    233.096  (_ 2.6%) i/s -      1.173k in   5.036040s

$ NO_SEQUEL_PG=1 DATABASE_URL=postgres:///?user=sequel_test ruby t.rb eager_initialize plugin
Warming up --------------------------------------
  Retrieve 1000 rows     8.000  i/100ms
Calculating -------------------------------------
  Retrieve 1000 rows     90.653  (_ 1.1%) i/s -    456.000  in   5.030471s
```

With an SQLite memory database and no plugins, the difference is over 35%:

```
$ ruby t.rb regular noplugin
Warming up --------------------------------------
  Retrieve 1000 rows     8.000  i/100ms
Calculating -------------------------------------
  Retrieve 1000 rows     84.154  (_ 1.2%) i/s -    424.000  in   5.039503s

$ ruby t.rb eager_initialize noplugin
Warming up --------------------------------------
  Retrieve 1000 rows     6.000  i/100ms
Calculating -------------------------------------
  Retrieve 1000 rows     61.862  (_ 1.6%) i/s -    312.000  in   5.044981s
```

and with plugins added, the difference is over 50%:

```
$ ruby t.rb regular plugin
Warming up --------------------------------------
  Retrieve 1000 rows     7.000  i/100ms
Calculating -------------------------------------
  Retrieve 1000 rows     73.384  (_ 2.7%) i/s -    371.000  in   5.059455s

$ ruby t.rb eager_initialize plugin
Warming up --------------------------------------
  Retrieve 1000 rows     4.000  i/100ms
Calculating -------------------------------------
  Retrieve 1000 rows     47.122  (_ 0.0%) i/s -    236.000  in   5.008490s
```

As you'll see by the benchmark, the reason the performance difference is much different than you would expect is that Model loads from the database in Sequel run through `Sequel::Model.call` (class method, not instance method), and all of the plugins that use instance variables must override the method to set the instance variables.  Because it is a class method and not an instance method, `instance_variable_set` must be used.  The overhead of all of those additional method calls (`super` and `instance_variable_set`) is what causes the dramatic difference in performance.

Feel free to play around with the above benchmark, but I hope it shows you that I'm asking for this feature for a good reason.

> So, for this case, how about using `def foo; @foo ||= nil; end`? That would avoid the warning and still let you assign the `@ivar` lazily, no?

Then you need a method call instead of an ivar access, which is also slower.  It also would require more changes across the library.

> I have another concern: hiding warnings like this will silently cause much worse performance in `$VERBOSE` mode.
> Admittedly this would only happen when `$VERBOSE` is true, but performance might still matter to some degree in such cases, e.g., when running tests (tests frameworks often enable $VERBOSE, and people still want their tests to run reasonably fast).
> 
> In such a case, on you branch it's 6.6 times slower for reads:
> https://gist.github.com/eregon/32f4119b7796ec7a6243c68990949597
> ```
> Comparison:
>          initialized: 14754531.3 i/s
>        uninitialized:  2227414.1 i/s - 6.62x  (± 0.00) slower
> ```

I think most Ruby users would trade faster production performance for slower tests.  Obviously, for those that wouldn't make that trade, they can initialize all instance variables and would not need to use this.
 
> I think in general it would be useful to have a thread-safe way to suppress warnings for a block of code, and that could be used in this case and many other cases (e.g., 194 `suppress_warning` in ruby/ruby).
> Unfortunately, changing `$VERBOSE` is not thread-safe if there are multiple Threads.
> That IMHO would be a valuable addition, and be useful not just for this method-redefinition-ignoring-previous case but in many other cases too.
> (it wouldn't be a good solution for the uninitialized ivar case, there I think it's really best to avoid the warning in the first place)

Possibly useful, but unrelated to the current proposal.  Please post a new feature request if you would like that.

----------------------------------------
Feature #17055: Allow suppressing uninitialized instance variable and method redefined verbose mode warnings
https://bugs.ruby-lang.org/issues/17055#change-86914

* Author: jeremyevans0 (Jeremy Evans)
* Status: Open
* Priority: Normal
----------------------------------------
These two verbose mode warnings are both fairly common and have good reasons why you would not want to warn about them in specific cases.  Not initializing instance variables to nil can be much better for performance, and redefining methods without removing the method first is the only safe approach in multi-threaded code.

There are reasons that you may want to issue verbose warnings by default in these cases.  For uninitialized instance variables, it helps catch typos. For method redefinition, it could alert you that a method already exists when you didn't expect it to, such as when a file is loaded multiple times when it should only be loaded once.

I propose we keep the default behavior the same, but offer the ability to opt-out of these warnings by defining methods.  For uninitialized instance variables in verbose mode, I propose we call `expected_uninitialized_instance_variable?(iv)` on the object.  If this method doesn't exist or returns false/nil, we issue the warning.  If the method exists and returns true, we suppress the warning.  Similarly, for redefined methods, we call `expected_redefined_method?(method_name)` on the class or module.  If the method doesn't exist or returns false/nil, we issue the warning.  If the method exists and returns true, we suppress the warning.

This approach allows high performance code (uninitialized instance variables) and safe code (redefining methods without removing) to work without verbose mode warnings.

I have implemented this support in a pull request: https://github.com/ruby/ruby/pull/3371

---Files--------------------------------
t.rb (5.59 KB)


-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>

  parent reply	other threads:[~2020-08-03 16:31 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-07-28 22:03 [ruby-core:99375] [Ruby master Feature#17055] Allow suppressing uninitialized instance variable and method redefined verbose mode warnings merch-redmine
2020-07-29  2:26 ` [ruby-core:99386] " shyouhei
2020-07-29  6:54 ` [ruby-core:99387] " kamipo
2020-07-29  9:17 ` [ruby-core:99388] " jean.boussier
2020-07-29 20:48 ` [ruby-core:99395] " eregontp
2020-07-29 20:55 ` [ruby-core:99396] " eregontp
2020-07-29 21:37 ` [ruby-core:99397] " merch-redmine
2020-07-30 18:04 ` [ruby-core:99400] " headius
2020-07-30 18:16 ` [ruby-core:99401] " headius
2020-08-01 14:36 ` [ruby-core:99439] " eregontp
2020-08-01 14:54 ` [ruby-core:99440] " eregontp
2020-08-02 15:38 ` [ruby-core:99445] " merch-redmine
2020-08-03 11:22 ` [ruby-core:99453] " eregontp
2020-08-03 16:30 ` merch-redmine [this message]
2020-08-03 16:48 ` [ruby-core:99458] " tenderlove
2020-08-03 17:01 ` [ruby-core:99459] " merch-redmine
2020-08-03 18:31 ` [ruby-core:99460] " tenderlove
2020-08-04  2:59 ` [ruby-core:99472] " merch-redmine
2020-08-15 10:54 ` [ruby-core:99595] " eregontp
2020-08-15 12:23 ` [ruby-core:99596] " eregontp
2020-08-15 15:42 ` [ruby-core:99597] " merch-redmine
2020-09-01 15:56 ` [ruby-core:99819] " merch-redmine
2020-09-02 14:57 ` [ruby-core:99844] " eregontp
2020-09-02 15:35 ` [ruby-core:99845] " merch-redmine
2020-12-02 21:25 ` [ruby-core:101207] " merch-redmine
2020-12-03 22:44   ` [ruby-core:101231] " Austin Ziegler
2020-12-03 23:04     ` [ruby-core:101233] " Jeremy Evans
2020-12-04 17:34       ` [ruby-core:101244] " Austin Ziegler
2020-12-10  4:35 ` [ruby-core:101353] " matz

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.ruby-lang.org/en/community/mailing-lists/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=redmine.journal-86914.20200803163055.1604@ruby-lang.org \
    --to=ruby-core@ruby-lang.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).