Path: csiph.com!fu-berlin.de!uni-berlin.de!not-for-mail From: Oscar Benjamin Newsgroups: comp.lang.python Subject: Re: Optimizing Memory Allocation in a Simple, but Long Function Date: Sun, 24 Apr 2016 22:55:34 +0100 Lines: 200 Message-ID: References: Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 X-Trace: news.uni-berlin.de O2TpaZkPTKxbewK/dMelAQgqEvwGqjuIk7nW8aqoV09w== Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.001 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'essentially': 0.04; 'linear': 0.07; 'smallest': 0.07; 'subject:Function': 0.07; 'accuracy.': 0.09; 'client:': 0.09; 'compute': 0.09; 'here?': 0.09; 'loop.': 0.09; 'okay': 0.09; 'rounding': 0.09; 'skip:\\ 30': 0.09; 'subject:Long': 0.09; 'way:': 0.09; 'python': 0.10; 'def': 0.13; 'result.': 0.15; '2016': 0.16; 'algorithm.': 0.16; 'algorithmic': 0.16; 'bouncing': 0.16; 'numerically': 0.16; 'quadratic': 0.16; 'received:io': 0.16; 'received:psf.io': 0.16; 'sequence.': 0.16; 'wrote:': 0.16; 'nested': 0.18; 'skip:l 30': 0.18; 'runs': 0.18; 'math': 0.20; 'algorithm': 0.20; 'to:name :python-list@python.org': 0.20; 'fix': 0.21; '(all': 0.22; 'see:': 0.22; 'am,': 0.23; 'bit': 0.23; 'accuracy': 0.23; 'this:': 0.23; 'import': 0.24; 'header:In-Reply-To:1': 0.24; 'mon,': 0.24; 'example': 0.26; 'chris': 0.26; 'error': 0.27; 'equivalent': 0.27; 'message-id:@mail.gmail.com': 0.27; 'sequence': 0.27; 'skip:e 30': 0.27; 'values': 0.28; 'initial': 0.28; 'implicitly': 0.29; "people's": 0.29; 'url:wikipedia': 0.29; 'code:': 0.29; 'classes': 0.30; 'url:wiki': 0.30; 'code': 0.30; 'e.g.': 0.30; 'putting': 0.30; 'error.': 0.31; 'skip:s 30': 0.31; 'skip:_ 10': 0.32; 'computing': 0.32; 'run': 0.33; 'class': 0.33; 'problem': 0.33; 'subject:Simple': 0.33; 'values.': 0.33; 'version:': 0.33; 'previous': 0.34; 'gives': 0.35; 'received:google.com': 0.35; 'i.e.': 0.35; 'replace': 0.35; 'something': 0.35; 'expected': 0.35; 'step': 0.36; 'but': 0.36; 'list,': 0.36; 'should': 0.36; 'instead': 0.36; 'url:org': 0.36; 'received:209.85': 0.36; 'smaller': 0.36; 'to:addr:python-list': 0.36; 'subject:: ': 0.37; 'method': 0.37; 'desired': 0.37; 'doing': 0.38; 'received:209': 0.38; 'skip:s 40': 0.38; 'stuff': 0.38; 'url:en': 0.39; 'enough': 0.39; 'takes': 0.39; 'skip:e 20': 0.39; 'to:addr:python.org': 0.40; 'where': 0.40; 'some': 0.40; 'your': 0.60; 'back': 0.62; 'skip:n 10': 0.62; 'is.': 0.63; 'more': 0.63; 'great': 0.63; 'url:%28': 0.66; 'url:%29': 0.66; 'here': 0.66; 'situation': 0.67; 'url:%1': 0.67; 'finally': 0.70; 'increase': 0.73; 'increasing': 0.76; 'confusing': 0.84; 'oscar': 0.84; 'quicker': 0.84; 'steps.': 0.91; 'increases': 0.93; 'instantly': 0.93; 'url:%2b': 0.95 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:from:date:message-id:subject:to; bh=9tbujVlOj8Yz5BG6ziyRZX77lgVZ6DDm+Vb1Mevmc2s=; b=cEmJFA027/RWYrvaQl12lASSY48UfuddRG2T9M3BmKP/QC0fB09796JRQbBoNLOTiy VUMcH9bF16MvdAllfzj8fYsplNOlrjzEio0NUQx6s0yCbftq1822W+28WOF6zwmzn3A6 NCtsyUArThG31KpUM5Y7oqBS+8ImjeqputJKDpMzBzaANGh6kbJ5C11S1tRCUFBadElX rBQXTKmnNWZZdXJi3JZIANq0cOIxuN0ehFfAhZnzGQR4R55WCQ/GNCedwnrCJdvmHwNV u4aPyAunnSO4SYraqByrbq5mxZ0icawApOF7WKWiwPzbJULQsvh4ewwV+Qpx04NOS7Fk DvdA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:in-reply-to:references:from:date :message-id:subject:to; bh=9tbujVlOj8Yz5BG6ziyRZX77lgVZ6DDm+Vb1Mevmc2s=; b=buLy38p95hIWSxvTsOD5FKpWtN6Myq6IpGI9DrdUjkuCubgeMfaol0f3Yefks91914 jzg2FCv1h6QP+9cKYcB2jKrUHBr/Lhl1ynY+9BJBDiNDDpFhA6Y1URVpHvhIKB2kjpIX vdrZ/5ZzShMA2zqYNt2F8BUj+2BGkqcNCPxy5bC01L+mYbo9CwJSZNWptKUFcfdW4S1z qizUaV4rGkhstArcFHic5Wqu2TCvClXs4Mn3BWKUoRzr+dbfkcHQ1Aoex23q6uvFZ7j3 4DVJK44bU+gdbdKaN13wV8oaXV38yDRal2OdG5dGW2UG9UbtSaLMXXLGDeuMZ16NI6z+ x8wA== X-Gm-Message-State: AOPr4FVFXR1fWxZCLU4HUU0HRUdCwlLGcAevpMw7WX33kzT4tuMzd5T/y1xjHIigK4qI+0S12L35xt6bMbjkkg== X-Received: by 10.112.209.73 with SMTP id mk9mr4622279lbc.121.1461534954209; Sun, 24 Apr 2016 14:55:54 -0700 (PDT) In-Reply-To: X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.22 Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Mailman-Original-Message-ID: X-Mailman-Original-References: Xref: csiph.com comp.lang.python:107573 On 24 April 2016 at 19:21, Chris Angelico wrote: > On Mon, Apr 25, 2016 at 4:03 AM, Derek Klinge wrote: >> Ok, from the gmail web client: > > Bouncing this back to the list, and removing quote markers for other > people's copy/paste convenience. > > ## Write a method to approximate Euler's Number using Euler's Method > import math > > class EulersNumber(): > def __init__(self,n): > self.eulerSteps = n > self.e = self.EulersMethod(self.eulerSteps) > def linearApproximation(self,x,h,d): # f(x+h)=f(x)+h*f'(x) > return x + h * d > def EulersMethod(self, numberOfSteps): # Repeate linear > approximation over an even range > e = 1 # e**0 = 1 > for step in range(numberOfSteps): > e = self.linearApproximation(e,1.0/numberOfSteps,e) # if > f(x)= e**x, f'(x)=f(x) > return e > > > def EulerStepWithGuess(accuracy,guessForN): > n = guessForN > e = EulersNumber(n) > while abs(e.e - math.e) > abs(accuracy): > n +=1 > e = EulersNumber(n) > print('n={} \te= {} \tdelta(e)={}'.format(n,e.e,abs(e.e - math.e))) > return e > > > def EulersNumberToAccuracy(PowerOfTen): > x = 1 > theGuess = 1 > thisE = EulersNumber(1) > while x <= abs(PowerOfTen): > thisE = EulerStepWithGuess(10**(-1*x),theGuess) > theGuess = thisE.eulerSteps * 10 > x += 1 > return thisE > > >> To see an example of my problem try something like EulersNumberToAccuracy(-10) Now that I can finally see your code I can see what the problem is. So essentially you want to calculate Euler's number in the following way: e = exp(1) and exp(t) is the solution of the initial value problem with ordinary differential equation dx/dt = x and initial condition x(0)=1. So you're using Euler's method to numerically solve the ODE from t=0 to t=1. Which gives you an estimate for x(1) = exp(1) = e. Euler's method solves this by going in steps from t=0 to t=1 with some step size e.g. dt = 0.1. You get a sequence of values x[n] where x[0] = x(0) = 1 # initial condition x[1] = x[0] + dt*f(x[0]) = x[0] + dt*x[0] x[2] = x[1] + dt*x[1] # etc. In order to get to t=1 in N steps you set dt = 1/N. So simplifying your code (all the classes and functions are just confusing the situation here): N = 1000 dt = 1.0 / N x = 1 for n in range(N): x = x + dt*x print(x) When I run that I get: 2.71692393224 Okay that's great but actually you want to be able to set the accuracy required and then steadily increase N until it's big enough to achieve the expected accuracy so you do this: import math error = 1 accuracy = 1e-2 N = 1 while error > accuracy: dt = 1.0 / N x = 1 for n in range(N): x = x + dt*x error = abs(math.e - x) N += 1 print(x) But what happens here? You have a loop in a loop. The inner loop takes n over N values. The outer loop takes N from 1 up to Nmin where Nmin is the smallest value of N such that we achieve the desired accuracy. This is a classic case of a quadratic performance algorithm. As you make the accuracy smaller you're implicitly increasing Nmin. However the algorithmic performance is quadratic in Nmin i.e. O(Nmin**2). The problem is the nested loops. If you have an outer loop that increases the length of an inner loop by 1 at each step then you have a quadratic algorithm akin to: # This loop is O(M**2) for n in range(N): for N in range(M): # do stuff To see that it is quadratic see: https://en.wikipedia.org/wiki/Triangular_number The simplest fix here is to replace N+=1 with N*=2. Instead of increasing the number of steps by one if the accuracy is not small enough then you should double the number of steps. That will give you an O(Nmin) algorithm. https://en.wikipedia.org/wiki/1/2_%2B_1/4_%2B_1/8_%2B_1/16_%2B_%E2%8B%AF A better method is to do a bit of algebra before putting down the code: x[1] = x[0] + h*x[0] = x[0]*(1+h) = x[0]*(1+1/N) = (1+1/N) x[2] = x[1]*(1+1/N) = (1+1/N)**2 ... x[n] = (1 + 1/n)**n So doing the loop for Euler's method is equivalent to just writing: x = (1 + 1.0/N)**N This considered as a sequence in N is well known as a sequence that converges to e. In fact this is how the number e was first discovered: https://en.wikipedia.org/wiki/E_%28mathematical_constant%29#Compound_interest Python can compute this much quicker than your previous version: N = 1 for _ in range(40): N *= 2 print((1 + 1.0/N) ** N) Which runs instantly and gives: 2.25 2.44140625 2.56578451395 2.63792849737 2.67699012938 2.69734495257 2.70773901969 2.71299162425 2.71563200017 2.71695572947 2.71761848234 2.71795008119 2.71811593627 2.71819887772 2.71824035193 2.7182610899 2.71827145911 2.71827664377 2.71827923611 2.71828053228 2.71828118037 2.71828150441 2.71828166644 2.71828174745 2.71828178795 2.71828180821 2.71828181833 2.7182818234 2.71828182593 2.71828182719 2.71828182783 2.71828182814 2.7182818283 2.71828182838 2.71828182842 2.71828182844 2.71828182845 2.71828182845 2.71828182846 2.71828182846 So your method is computing the above numbers but in a slower way that also has more potential for rounding error. The error here is 1e-13 for the last numbers in this sequence. But N=2**40 so your Euler method would need approximately 10**12 iterations in your inner loop to get the same result. That's going to be slow even if you don't use a quadratic algorithm. -- Oscar