High Performance Python

Errata for High Performance Python

Submit your own errata for this product.


The errata list is a list of errors and their corrections that were found after the product was released. If the error was corrected in a later version or reprint the date of the correction will be displayed in the column titled "Date Corrected".

The following errata were submitted by our customers and approved as valid errors by the author or editor.

Color Key: Serious Technical Mistake Minor Technical Mistake Language or formatting error Typo Question Note Update



Version Location Description Submitted By Date Submitted Date Corrected
Safari Books Online
?
Last paragraph of section 1.2 Putting the Fundamental Elements Together

The second sentence of the last paragraph of section 1.2 Putting the Fundamental Elements Together says, "For example, let's assume we change the code to use multiple CPU cores such that each core gets a chunk of the numbers from 1 to sqrtN." This should be from 2 to sqrtN as it is in the code above it, not 1. For the example problem, starting at 1 as a factor of a prime number will always yield incorrect results.

Note from the Author or Editor:
Changed the starting value from 1 to 2

Mike Mabey  Jul 01, 2014 
PDF
Page 37
3rd to last para

"On the next line we see that the process grew by approximately a further 48MB inside the loop, this will be accounted for by all the unique floating point numbers that are generated inside the complex z updates in the innermost loop." I have two questions about this statement. First of all, "On the next line" seems to refer to line 13, since the para above is talking about line 12. Line 13 of the output shows about 33MB, not 48MB, being allocated. I don't see 48MB anywhere in the listing. So perhaps the "48" is from an earlier draft? It isn't clear. Second of all, how do you know that that 33MB allocation on line 13 is due to the "unique floating point numbers generated inside the complex z updates"? After all, line 13, is this bit of code: for i in range(len(zs)): That makes it seem like the 33MB is due to allocating range(len(zs)), which presumably is a big static list that gets created all at once. By contrast, the complex z updates happen in line 18 of the listing: z=z*z+c This line (18) has 0MB incrementally associated with it. So can you please clarify: (1) what 48MB are you referring to; and (2) how can you attribute it to the z updates, rather than to the range(len(zs))?

Note from the Author or Editor:
Already fixed

dml  Jun 16, 2014 
PDF
Page 37
2nd para from bottom

"RAM allocation in this function is fairly low." That sounds absurd. According to the listing, this function requires 32MB of RAM just to iterate over an array. In what world is that considered a small amount of memory? Maybe you're comparing to lots of other Python code, but to somebody coming from a C or Fortran background, this looks pretty bloated.

Note from the Author or Editor:
Already fixed

dml  Jun 16, 2014 
PDF
Page 41
last para of text

"Note the call to hpy.setrelheap() ..." The code listing contains no call to hpy.setrelheap().

Note from the Author or Editor:
Fixed in Rachel Head's PDF

dml  Jun 18, 2014 
PDF
Page 44
last para

"we can keep the console open using the sleep," I assume by "the sleep" you mean "the call to time.sleep".

Note from the Author or Editor:
Edit marked in PDF for Rachel Head

dml  Jun 18, 2014 
PDF
Page 51
bullet list under "Strategies to profile your code"

Bullet points mix present and past tense. Suggest consistency. Bullets 1, 2, 5, 6 use present tense: disable, disable, run reboot. Bullets 3, 4 use past tense: used, disabled.

Note from the Author or Editor:
Fixed in Rachel Head's PDF

dml  Jun 21, 2014 
ePub
Page 59.0/330
timefn example

In the example of timefn, there should be a new version of calc_pure_python that uses the decorated version of calculate_z_serial_purepython. In its absence (i.e. if you use calc_pure_python as is) you get two timings, the timing based on the decorator, then the timing around it based on calc_pure_python time.time() calls.

Note from the Author or Editor:
Already fixed

Louis Luangkesorn  Jun 09, 2014 
PDF
Page 60
Last para, and first para on p.61

Note this comment refers to early release v4. My earlier comments refer to early release v3. The description of the "buckets" in a list/array goes back and forth between describing the buckets as holding the data, and as holding pointers to the data. Specifically, this sentence states that the buckets store the data: "In order to look up any specific element in our list, we simply need to know which element we want and remember which bucket our data started in." The next sentence states that the buckets actually store a pointer to the data: "Since all of the data will occupy the same amount of space (one “bucket” or, more specifically, one integer sized pointer to the actual data), we don’t need to know anything about the type of data that is being stored to do this calculation." (By the way, that should be "integer-sized".) The next two sentences revert to describing the buckets as storing the data: "If, for example, we needed to retrieve the first element in our array, we simply go to the first bucket in our sequence, M, and read out the value inside it. If, on the other hand, we needed the 5th element in our array, we simply need to go to the M+5 ‘th bucket and read its content." I suppose you're trying to avoid being pedantic, but it should be pretty easy to make clear that consecutive buckets store pointers to the actual data, and stick with that description throughout.

Note from the Author or Editor:
Added a note making sure the reader understands that a block is in fact a pointer to data.

dml  Jun 23, 2014 
PDF
Page 62
2nd para under "More efficient search"

"The two elements necessary are the sorting algorithm and then the searching algorithm." Since the word "elements" is used repeatedly in the surrounding text with its technical meaning of "a single entry in a list or array", consider changing "elements" here to some other word, e.g., "pieces" or "ingredients" or "routines"-- anything like that.

Note from the Author or Editor:
Changed elements to ingredients.

dml  Jun 24, 2014 
PDF
Page 62
3rd para under "More efficient search"

"Once a list has been sorted, we can use a binary search algorithm (Example 3-3) to find our desired element, which has an average case complexity of O(logn). It achieves this by first looking at the middle of the list and comparing this value with the desired value. If this midpoint’s value is less than our desired value, then we consider the left half of the list and continue halving the list like this until the value is found." The algorithm in Example 3-3 looks at the middle, and "If this midpoint's value is less than our desired value, then we consider the RIGHT half of the list." As long as I'm on this paragraph, note that the first sentence interposes "our desired element" into the phrase "binary search algorithm, which has an average complexity..." Better would be something like "... we can find our desired element using binary search, which has ..." Also, note that the algorithm continues "halving the list like this until the value is found, or until the value is known not to occur in the sorted list."

Note from the Author or Editor:
Marked in the PDF to Rachel

dml  Jun 24, 2014 
PDF
Page 62
Last para before "More efficient search"

"... by disregarding any intrinsic ordering to the data and instead ascribing another, more peculiar, organization." This all sounds very odd. First of all, data structures don't really "ascribe" an ordering, they impose an ordering. "Ascribe" is pretty passive-- it just says that the data structure identifies the cause of some ordering that's already out there somehow. Second, "peculiar" is a strange word to use. Why is a dictionary's ordering any more strange ("peculiar") than whatever other order might be imposed? I think you mean "specific" or "particular" or something down that line. Finally (and this is a minor quibble), a lot of data don't have an "intrinsic" ordering. For example, if the dictionary maps words harvested from a text to the count of times each word appears, there isn't any particular "intrinsic" order to the words (or rather, one could identify several different "intrinsic" orderings, depending on the application).

Note from the Author or Editor:
Changed ascribe to specify and "any intrinsic ordering to" to "the original ordering of"

dml  Jun 23, 2014 
PDF
Page 63
Example 3-4

"# bisect.bisect_left will return the first value in the haystack that is greater # than or equal to the needle" Looking at the code that follows, bisect.bisect_left actually returns the index to that value, rather than the value itself.

Note from the Author or Editor:
marked in PDF to rachel

dml  Jun 24, 2014 
PDF
Page 65
2nd to last para of chapter

"abstract code will be much slower than code specifically designed to solve a particular problem." You probably mean "general code" rather than "abstract code"-- "abstract" is not the opposite of "particular". I would especially avoid "abstract" since the term has a loaded meaning in some object-oriented languages (where an abstract method gives a pattern, but not an implementation).

Note from the Author or Editor:
Changed 'abstract' to 'general'

dml  Jun 24, 2014 
PDF
Page 65
2nd to last para of chapter

"These overheads can be removed if we also force all the data in our list to be of the same type." This sentence doesn't need the "also", since it just makes explicit the advice offered by the sentence before. In addition, it's not clear why this sentence mentions only lists, when the sentence before seems to be talking about tuples as well. Maybe there is some deeper idea here that you're trying to put across?

Note from the Author or Editor:
Changed sentence to: These overheads can be removed if we force all of our data to be of the same type, which would allow us to use the `array` module, a `numpy.ndarray` or several other such objects.

dml  Jun 24, 2014 
PDF
Page 70
All of chapter 3

With all the space this chapter devotes to describing the problem with appending items to lists, I was surprised not to see anything about pre-allocating a list. Many dynamic languages have a mechanism to declare an empty structure with a given size, in order to allocate sufficient memory up-front. Does Python not have such a feature? Or are you planning to talk about it later? (Or did I just miss it somehow?) If you do talk about it later, maybe give a forward pointer to the fact that you'll do so.

Note from the Author or Editor:
In the chapter describing matrix computations (particularly the section on python lists), we address this issue in detail. I added a reference to it in the wrap-up section of the lists/tuple chapter.

dml  Jun 25, 2014 
PDF
Page 71
first para

"...sets do not actually contain data, they are simply a collection of unique keys." If you're using a set to store keys, then the keys are almost certainly the data that interest you. Maybe a better word than "data" is "values" since this sentence is meant to contrast the keys in sets with the key-value pairs in dictionaries.

Note from the Author or Editor:
Changed to: ... sets do not actually contain values ...

dml  Jun 27, 2014 
PDF
Page 71
last para

"before we were restricted to, at best, O(log n) lookup time on lists/tuples with no intrinsic order" I don't think "no intrinsic order" is correct here. You clearly mean to indicate that the user doesn't know the index of the item of interest, so the item can't be accessed in O(1) time. However, not knowing the index does not mean that the items have "no intrinsic order". To get that O(log n) behavior requires the items to be sorted, which implies there is some meaningful way to order the data. If the items truly have no intrinsic order, then search is O(n).

Note from the Author or Editor:
Change O(log n) to O(n)

dml  Jun 27, 2014 
PDF
Page 74
Section "How do dictionaries and sets work?"

This entire section assumes the reader knows what hashing means. But you never come out and define it, exactly. Instead, there are two sentences that hint around about it: "This efficiency is a result of a very clever usage of hash functions [Link to Come] in order to create list-like indexes for data. By turning the data’s key into something that can be used like a list index..." If you don't already know what a hash function and a hash table are, that first sentence can create the impression that a hash function is something that is used in a clever way to create indices. That's needlessly indirect-- the hash function itself creates the indices. It's not some function, designed for another purpose, that is somehow cleverly bootstrapped into creating indices. Another thing I find vague about that first sentence is the phrase "list-like indexes." I know you mean "an index like the kind used for lists" but that is NOT what "list-like indexes" means. "List-like index" means an index that is somehow like a list. But indices are not like lists. They are like integers. In fact, they ARE integers. So just call them integers. After that, if you really want to remind the reader that integers are also used to index lists, go ahead and do it. The second sentence is also vague. Why is the data's key "turned into something that can be used like a list index", as opposed to being turned into an integer index? Again, a direct description would be better. And again, a "list index" is an odd phrase, because it implies that there are multiple types of indices, one of which is called a "list index". Might be better to say "an index into a list." That (intended) link to a section on hash functions compounds the vagueness. For readers who don't know what a hash function is, that link will make them think that you aren't actually defining hash functions here, and that they need to click forward in order to learn about them. It may create the impression that these two sentences aren't really about hash functions, after all, but about the way hash functions are used. So I would encourage you to be direct-- make plain that you're actually defining hash functions (and, by extension, hash tables) here. And then use direct, straightforward descriptions, rather than just describing properties of hash functions. In short, you need to bite the bullet and just define the hash function in a straightforward way. Then describe how the hash function is used to construct the table.

Note from the Author or Editor:
Changed the sentence at hand to: This efficiency is the result of a very clever usage of hash functions to turn an arbitrary key (ie: a string or object) into an index for a list. The hash function and list can later be used to know where any particular piece of data is right away without a search.

dml  Jun 27, 2014 
PDF
Page 75
Second para under "Inserting and Retrieving"

"So, if we have allocated 8 blocks of memory and our hash value is 28975, we consider the bucket at index 28975 & 0b111 = 7. If, however, our dictionary has grown to have 256 items then the mask becomes 0b11111111." Question-- First sentence says that the mask is based on the number of blocks allocated, while the second sentence implies the mask depends on the number of items stored in the dictionary. If both sentences are correct, it implies the dictionary only allocates memory as needed, i.e., it never over-allocates memory in the fashion of lists. This seems unlikely (especially because hash tables often start with a significant amount of memory allocated, in order to allow the hash function to do a good job spreading the indices out, to avoiding hitting the same indices over and over). So the question is, do Python dictionaries only allocate just enough memory for the items stored, or is the second sentence wrong?

Note from the Author or Editor:
Changed text to: If, however, our dictionary has grown to contain 512 blocks of memory

dml  Jun 28, 2014 
PDF
Page 81
2nd para before Example 4-6

"For example, for a dictionary with 4 elements, the mask we use is 0b111." Again there's this ambiguity about how the mask gets chosen-- based on the number of elements in the dictionary, or based on the amount of memory allocated to the dictionary. From section "Resizing" on p79, it seems like the mask is changed only when the allocated memory is changed. That matches my intuition about how hashes work. But the sentence quoted above implies that the mask depends on the number of elements in the dictionary. Please clarify.

Note from the Author or Editor:
A note has been added describing how to find the mask given the current length of the dictionary.

dml  Jun 28, 2014 
PDF
Page 85
last para

"In the next chapter, we will explore generators which allows us to provide data to code in any dynamic order we want and without necessarily storing it in memory beforehand." This sentence is a bit unclear. Here are the things I know to be wrong, just from the rules of English: -- "generators which" --> "generators, which" -- "generators, which allows" --> "generators, which allow" There are two other bits I have trouble with (since I don't know generators well enough): -- "provide data to code" I think this means you can provide data to the program, but since "generator" could be a Lisp-like ability to define high-level macros, it could also mean that there is some way to convert data into code. -- "storing it" The "it" is a weak reference. I assume "it" refers to "data", meaning you don't have to store the data in memory beforehand. But I'm not sure, since so much has happened, in terms of language, between "data" and "it".

dml  Jun 28, 2014 
PDF
Page 88
First full para

"So, if the range is from 1 to 10,000, the function will do 10,000 appends to the numbers list... As a result, ... the range version of the loop above uses 10x more memory..." This reads oddly because, near the top of the paragraph, the example runs "range" from 1 to 10000, while the end of the paragraph says there's a factor of 10 difference. That factor of 10 refers to the code, which runs "range" from 1 to 10. Suggest you make the example in the text match the sample code (or vice-versa). I.e., make the code say "for i in range(1, 10000)", or else make the example in the paragraph say "So, if the range is from 1 to 10, ..."

Note from the Author or Editor:
Change the example to range from 1 to 10000.

dml  Jun 29, 2014 
PDF
Page 93
last para

"if the max is 3 sigma larger than the mean" --> "if the max is more than 3 sigma larger than the mean"

Note from the Author or Editor:
Made suggested change.

dml  Jun 30, 2014 
PDF
Page 95
2nd to last para

"This can be changed by opening the file multiple time and using this to point to the parts of the data we like" It isn't clear to what the second "this" refers. In other words _what_ gets used to point to parts of the data?

Note from the Author or Editor:
Changed sentence to: This can be changed by opening the file multiple times and using the various file descriptors to point to the exact data we like (or use te linecache module), ...

dml  Jun 30, 2014 
PDF
Page 99
bird icon para

"So, instead of solving for how a couple drops of dye diffuses through water, we would be solving for how the heat from several drops of hot water diffuses through cold water." Not the best example for heat diffusion. If you put drops of hot water into cold water, the physical diffusion of water molecules is going to dominate the conduction of heat by water. A better example would be a solid. Why not a computer analogy-- like this: "So, instead of solving for how a couple of drops of dye diffuse through water, we might be solving for how the heat generated by a CPU diffuses into a heat sink."

Note from the Author or Editor:
Changed text as per suggestion.

dml  Jul 02, 2014 
PDF
Page 99
para after Euler's discretization

"In fact, as dt approaches zero, Euler’s approx‐ imation becomes exact." This is only true in infinite-precison arithmetic. Since this book is explicitly about computer approximations, using a language that employs finite-precision arithmetic, this statement is not true. In finite-precision arithmetic, as dt approaches zero, truncation errors of another kind start to increase the error made by each step.

Note from the Author or Editor:
While that is not a subject for this book, I will add a note stating that setting dt arbitrarily close to 0 only works theoretically and that there are limitations for doing this on the computer.

dml  Jul 02, 2014 
PDF
Page 100
2nd para under spatial discretization

The description of the "matrix" boundary conditions is a little loose. For example, the discretization you're talking about describes a vector, not a matrix, of values. If it was a matrix, you couldn't just talk about "index N-1" and so forth. I realize the reason for this is that you are keeping in mind that normally one discretizes 2D or 3D space, rather than a line. But you might want to think about making the description more internally-consistent. Another thing I'm not crazy about is the reference to "index N+1". For the N-dimensional vector you're describing, index N is the first element outside the bounds of the problem, and that index "wraps" to index 0. Since the discretization doesn't invoke more than one cell beyond that where the derivative is being approximated, there's no need to bring in a fictitious cell N+1 that the algorithm will never try to access.

Note from the Author or Editor:
Made specific reference to a particular dimension in the matrix for the boundary condition. In addition, changed the N+1 example to simply N.

dml  Jul 03, 2014 
PDF
Page 100
Example 6-1

"D = 1" Not sure why you define D when the code never uses it.

Note from the Author or Editor:
add D factor to second term in unew update.

dml  Jul 03, 2014 
PDF
Page 100
Example 6-1

"Example 6-1. Psudocode for Matrix Initialization for 1D Diffusion" Caption is not correct. Example 6-1 implements 1D diffusion. It does not merely initialize the matrix.

Note from the Author or Editor:
Changed caption to: Pseudocode for 1D diffusion

dml  Jul 04, 2014 
PDF
Page 104
Example 6-3

Function evolve() takes {grid} as an argument, uses it to calculate {new_grid}, then returns {grid}. It ought to return {new_grid}.

Note from the Author or Editor:
Changed: return grid to return new_grid

dml  Jul 05, 2014 
PDF
Page 104
Example 6-3

This line of code new_grid = [[0.0 for x in xrange(ymax)] for x in xrange(xmax)] works OK. However, it might be easier to read/understand if the variable in the "inner" comprehension was y rather than x: new_grid = [[0.0 for y in xrange(ymax)] for x in xrange(xmax)]

Note from the Author or Editor:
Changed new_grid = ... to: new_grid = [[0.0,]*ymax for x in xrange(xmax)]

dml  Jul 05, 2014 
PDF
Page 104
Example 6-3

It might be nice to remind us somewhere that kernprof.py requires the @profile decorator on function evolve().

Note from the Author or Editor:
Added note after example 6-5 about decorating functions with @profile.

dml  Jul 06, 2014 
PDF
Page 106
function loop_fast()

The loop shown in loop_fast() would benefit from even more aggressive removal of work from the loop. The loop finds the sum of a bunch of terms times a constant factor: result = sum_{i=0}^{num_iterations-1} i*factor This can be rewritten as result = factor * sum_{i=0}^{num_iterations-1} i In other words, hoist the entire multiplication out of the loop. Then you just have something like factor * sum(xrange(num_iterations)) Of course, the sum itself has an easy arithmetic reduction, so if you pursue this too far you ruin the point of the example.

Note from the Author or Editor:
While I completely agree, this was meant to be a trivial example. Further optimizing the code removes takes away the point. Using the arithmetic series it could always just be written as, return sin(num_iterations) * num_iterations * (num_iterations + 1) / 2.0

dml  Jul 06, 2014 
PDF
Page 112
introduction of bumpy package

Looking at the paragraph that starts "Luckily, the numpy package..." Since this introduces numpy, and doesn't relate directly to "Making decisions with perf", you might consider starting a new section here. In fact, you might label such a new section "Enter numpy", and then rename the section that currently has that title (on p. 115) something like "Applying numpy to the diffusion problem". This would make plain that you're returning to the original, motivating, example. By page 115, it's been something like 8 pages since you touched the diffusion example, and a little reminder that that's what we're working on wouldn't be amiss.

Note from the Author or Editor:
As requested, a new subsection will start on page 115 and the subsequent section heading will have a more informative title.

dml  Jul 15, 2014 
PDF
Page 114
first para

"the array and pure python functions take ~100,000,000,000 instructions while the numpy version take ~3,000,000,000 instructions." First, "numpy version takes" (add 's') Second, consider using scientific notation for numbers, to make the difference more clear: "the array and pure python functions take ~1x10^{11} instructions while the numpy version take ~3x10^9 instructions."

Note from the Author or Editor:
Fixed grammatical mistake and changed to scientific notation (good call!).

dml  Jul 16, 2014 
ePub
Page 172.0/330
United States

The function fibonacci_generator() has an error def fibonacci_generator(): count = 0 for f in fibonacci(): if f > 5000: break if j % 2: count += 1 return count In the second if statement, if j % 2: should be if f % 2:

Louis Luangkesorn  Jun 09, 2014