#30563: django.forms.widgets.Media.__add__ Performance Issue
-------------------------------------+-------------------------------------
               Reporter:  didorothy  |          Owner:  nobody
                   Type:             |         Status:  new
  Cleanup/optimization               |
              Component:  Forms      |        Version:  master
               Severity:  Normal     |       Keywords:  performance,
           Triage Stage:             |  unintended consequences
  Unreviewed                         |      Has patch:  0
    Needs documentation:  0          |    Needs tests:  0
Patch needs improvement:  0          |  Easy pickings:  0
                  UI/UX:  0          |
-------------------------------------+-------------------------------------
 While working with another project that make extensive use of
 `django.forms.widgets.Media` I discovered that the fix for ticket #30153
 has unintended consequences on the performance of `Media.__add__`. If the
 number of Media objects added grows beyond a certain point (not sure it
 may be machine specific) then the performance of all subsequent
 `Media.__add__` calls becomes terrible.

 This was causing page load times of several minutes on my development
 machine. I agree that you need to delay as long as possible as #30153
 intends but it seems that there probably should be an upper bound on this
 so that performance does not suddenly decrease.

 Here is some sample code that can reproduce the issue:

 {{{
 from django.forms import Media
 import datetime

 def create_media(MediaClass):
     '''Creates a simple Media object with only one or two items.'''
     return MediaClass(css={'all': ['main.css']}, js=['main.js'])

 start = datetime.datetime.now()
 media = create_media(Media)
 for i in range(100000):
     media = media + create_media(Media)

 print('100000 additions took: %s' % (datetime.datetime.now() - start))
 }}}

 On my machine several runs of this code result in times between 1:35 -
 1:44 (eg. 1 minute 35 seconds to 1 minute 44 seconds). However, taking
 away one zero from the number of media objects runs in under a second.
 Near as I can tell this has to do with the memory used to store these
 arrays and when it gets past a certain point the performance is awful.
 Since I am not sure if it is machine specific, for reference my machine is
 a i7-8700 with 64 GB RAM.

 Here is a sample that has a modified Media class that does not have theses
 issues:

 {{{
 from django.forms import Media
 import datetime

 def create_media(MediaClass):
     '''Creates a simple Media object with only one or two items.'''
     return MediaClass(css={'all': ['main.css']}, js=['main.js'])

 class CustomMedia(Media):
     def __add__(self, other):
         combined = CustomMedia()
         if len(self._css_lists) + len(other._css_lists) > 1000:
             combined._css_lists = [self._css, other._css]
         else:
             combined._css_lists = self._css_lists + other._css_lists

         if len(self._js_lists) + len(other._js_lists) > 1000:
             combined._js_lists = [self._js, other._js]
         else:
             combined._js_lists = self._js_lists + other._js_lists

         return combined

 start = datetime.datetime.now()
 media = create_media(CustomMedia)
 for i in range(100000):
     media = media + create_media(CustomMedia)

 print('100000 additions took: %s' % (datetime.datetime.now() - start))
 }}}

 With this change it again runs in under a second. If I increase the number
 of loops the performance seems to change at a much more expected level.

 I set an upper limit on the length allowed before a merge occurs. I'm not
 sure if the number of additions allowed should be a setting that can be
 adjusted to meet specific needs or if something that is just "reasonably"
 high is sufficient. It does appear that limiting the total number of items
 in the list to about 1000 works best on my machine. I'm also not sure that
 this is the best solution.

 Thanks for your consideration.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/30563>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/052.412a1c59baea95a7aa46e980197a2c1b%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to