<?xml version='1.0' encoding='UTF-8'?><rss xmlns:atom='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' version='2.0'><channel><atom:id>tag:blogger.com,1999:blog-36456651</atom:id><lastBuildDate>Mon, 08 Mar 2010 13:56:08 +0000</lastBuildDate><title>Dabeaz</title><description>Dave Beazley's  mondo computer blog.</description><link>http://www.dabeaz.com/blog/dablog.html</link><managingEditor>noreply@blogger.com (Dave)</managingEditor><generator>Blogger</generator><openSearch:totalResults>18</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-7873195459855819841</guid><pubDate>Fri, 26 Feb 2010 20:56:00 +0000</pubDate><atom:updated>2010-03-08T05:56:08.440-08:00</atom:updated><title>Upcoming Python Training Classes</title><description>&lt;p&gt;Please forgive the brief commercial interruption.  I'd just like to plug a few of my upcoming Python training classes--yes, if you must know, this is how I pay the bills so that I can spend the rest of my time thinking about the &lt;a href="http://www.dabeaz.com/GIL"&gt;GIL&lt;/a&gt; and other diabolical Python-related topics.&lt;/p&gt;&lt;p&gt;&lt;b&gt;New! Python Mastery Bootcamp, April 12-16, 2010 (Atlanta)&lt;/b&gt;&lt;/p&gt;&lt;p&gt;First, I'm pleased to announce a brand-new Python course that I'm offering for the first time at &lt;a href="http://www.bignerdranch.com"&gt;Big Nerd Ranch&lt;/a&gt; in Atlanta.   The &lt;a href="http://bignerdranch.com/classes/python.shtml"&gt;Python Mastery Bootcamp&lt;/a&gt; might be the ultimate Python tutorial for programmers who already know the basics of Python, but who want to take their understanding of the language to a whole new level.  Over the past few years, I have given a number of well-reviewed PyCON tutorials on advanced topics such as &lt;a href="http://www.dabeaz.com/generators"&gt;Generator Tricks for Systems Programmers&lt;/a&gt;, &lt;a href="http://www.dabeaz.com/coroutines"&gt;A Curious Course on Coroutines and Concurrency&lt;/a&gt;, or most recently &lt;a href="http://www.dabeaz.com/python3io"&gt;Mastering Python 3 I/O&lt;/a&gt;. Well, the Mastery Bootcamp is sort of similar except that it lasts 5 days, it covers far more material (network programming, threads, multiprocessing, asynchronous I/O, functional programming, metaprogramming, distributed computing, C extensions, etc.), and it has more hands-on projects that allow the material to be explored in greater depth than at a conference. &lt;/p&gt;&lt;p&gt;The experience at Big Nerd Ranch is quite unique--for 5 days, you will be completely immersed in Python programming without the annoyance of outside distractions.  This makes it the perfect environment to interact with other class participants and to really focus on the course material.  There's really nothing quite like it in the training world--you won't be disappointed.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Introduction to Python Programming, March 16-18, 2010 (Chicago)&lt;/b&gt;&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;If you're relatively new to Python and want to master the fundamentals, consider coming to my &lt;a href="http://www.dabeaz.com/chicago/index.html"&gt;Introduction to Python Programming&lt;/a&gt; class in Chicago.   This course is aimed at programmers, system administrators, scientists, and engineers who want to apply Python to everyday tasks such as analyzing data files, automating system tasks, scraping web pages, using databases, and more.  Through practical examples, you will learn all of the major features of Python including data handling, functions, modules, classes, generators, testing, and more.   This is a highly refined class that has been taught for numerous corporate and government clients over the past three years.  The class features a 300 page fully indexed course guide and more than 50 hands-on exercises.&lt;/p&gt;&lt;p&gt;My Chicago classes are also taught in a rather unique format.  Unlike a typical corporate training course, I conduct the course in a round-table format that is strictly limited to 6 attendees--a size that encourages interaction and allows course topics to be easily customized to your interests.   The course is located in Chicago's distinctive Andersonville neighborhood where just steps away, you will find dozens of unique restaurants, bakeries, coffee houses, pubs, and more.  You're definitely going to like it!&lt;/p&gt;&lt;p&gt;&lt;b&gt;March 8, 2010 update!&lt;/b&gt; Just a quick note to say that there are still a few slots available in the March 16-18 class.  Come to Chicago and celebrate St. Patrick's day by writing Python code.  Although the course is next week--last minute airfare and hotel deals are easy to find.  It's going to be a great class.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-7873195459855819841?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2010/02/upcoming-python-training-classes.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>1</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-8935811723349955313</guid><pubDate>Mon, 22 Feb 2010 21:47:00 +0000</pubDate><atom:updated>2010-02-22T13:47:27.180-08:00</atom:updated><title>Revisiting thread priorities and the new GIL</title><description>&lt;p&gt;Well, PyCon is over and it's time to get back to work.  First, I'd just like to thank everyone who came to my &lt;a href="http://www.dabeaz.com/python/UnderstandingGIL.pdf"&gt;GIL Talk&lt;/a&gt; and participated in all of the discussion that followed.    It was almost as if part of PyCon had turned into a mini operating systems conference!&lt;/p&gt;&lt;p&gt;This post is a followup to the GIL open space at PyCon where we looked at the new GIL and explored the possibility of introducing thread priorities.  For those of you not at PyCon, the open space was attended by about 30-40 people and included Guido, Antoine Pitrou, and a large number of systems hackers, some of which had previously worked on thread library implementations and operating system kernels.&lt;/p&gt;&lt;p&gt;First, a little background.   As might know, Antoine Pitrou implemented a new Python GIL that is currently only available in the Python 3.2 development branch (you can obtain it via subversion).   This new GIL is described in his &lt;a href="http://mail.python.org/pipermail/python-dev/2009-October/093321.html"&gt;original mailing list post&lt;/a&gt; as well as the &lt;a href="http://www.dabeaz.com/python/UnderstandingGIL.pdf"&gt;slides&lt;/a&gt; for my PyCon talk.  You should read those first if you haven't already.&lt;/p&gt;&lt;p&gt;Right before PyCON, I discovered an I/O performance problem with the new GIL that is related to CPU-bound threads stalling the progress of I/O bound threads which it turn leads to a severe performance degradation of I/O bandwidth and response time.  This is described in &lt;a href="http://bugs.python.org/issue7946"&gt;Issue 7946 : Convoy effect with I/O bound threads and New GIL&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;In the bug report, I submitted a very simple test case that illustrated the problem.  However, here is a more refined experiment that you can try.  The following program, &lt;tt&gt;iotest.py&lt;/tt&gt; contains both CPU-bound threads and an I/O server thread that echos UDP packets.  It is meant to study the case in which CPU-processing and I/O processing are overlapped. &lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;# iotest.py

import time
import threading
from socket import *
import itertools

def task_pidigits():
    """Pi calculation (Python)"""
    _map = map
    _count = itertools.count
    _islice = itertools.islice

    def calc_ndigits(n):
        # From http://shootout.alioth.debian.org/
        def gen_x():
            return _map(lambda k: (k, 4*k + 2, 0, 2*k + 1), _count(1))

        def compose(a, b):
            aq, ar, as_, at = a
            bq, br, bs, bt = b
            return (aq * bq,
                    aq * br + ar * bt,
                    as_ * bq + at * bs,
                    as_ * br + at * bt)

        def extract(z, j):
            q, r, s, t = z
            return (q*j + r) // (s*j + t)

        def pi_digits():
            z = (1, 0, 0, 1)
            x = gen_x()
            while 1:
                y = extract(z, 3)
                while y != extract(z, 4):
                    z = compose(z, next(x))
                    y = extract(z, 3)
                z = compose((10, -10*y, 0, 1), z)
                yield y

        return list(_islice(pi_digits(), n))

    return calc_ndigits, (50, )

def spin():
    task,args = task_pidigits()
    while True:
       r= task(*args)

def echo_server():
    s = socket(AF_INET, SOCK_DGRAM)
    s.setsockopt(SOL_SOCKET, SO_REUSEADDR,1)
    s.bind(("",16000))
    while True:
        msg, addr = s.recvfrom(16384)
        s.sendto(msg,addr)  

# Launch threads (adjust the number to see different results)
NUMTHREADS = 1
for n in range(NUMTHREADS):
    t = threading.Thread(target=spin)
    t.daemon = True
    t.start()

# Launch a background echo server
echo_server()
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Next, here is a client program &lt;tt&gt;ioclient.py&lt;/tt&gt; that simply measures the time it takes to echo 10MB of data to the server in the &lt;tt&gt;iotest.py&lt;/tt&gt; program.&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;# echoclient.py
from socket import *
import time

CHUNKSIZE = 8192
NUMMESSAGES = 1280     # Total of 10MB

# Dummy message
msg = b"x"*CHUNKSIZE

# Connect and send messages
s = socket(AF_INET,SOCK_DGRAM)
start = time.time()
for n in range(NUMMESSAGES):
    s.sendto(msg,("",16000))
    msg, addr = s.recvfrom(65536)
end = time.time()
print("%0.3f seconds (%0.3f bytes/sec)" % (end-start, (CHUNKSIZE*NUMMESSAGES)/(end-start)))
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;If you run &lt;tt&gt;iotest.py&lt;/tt&gt; on a dual-core Macbook with only 1 spin() thread.  You get the following result if you run &lt;tt&gt;ioclient.py&lt;/tt&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Python 3.2 (New GIL) : 9.166 seconds (1143998.140 bytes/sec)&lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;P&gt;It works, but it's hardly impressive (just barely over 1MB/sec transfer rate between two processes?).  However, if you make the server have two spin() threads, the performance gets much worse:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Python 3.2 (New GIL) : 28.064 seconds (373642.858 bytes/sec)&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Now to further complicate matters, if you disable all but one of the CPU cores, you get this inexplicable result:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Python 3.2 (New GIL, 1 CPU) : 0.297 seconds (35326299.028 bytes/sec)&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Needless to say, there are many bizarre things going on here.   The most major effect is that on multiple cores, it is very easy for CPU-bound threads to reacquire the GIL whenever an I/O bound thread performs I/O.   This means that CPU-threads have a greater tendency to hog the GIL.&lt;/p&gt;&lt;p&gt;At PyCON, I did some experiments with thread priorities and a modified GIL that adjusted priorities in a manner similar to what you find with multilevel feedback queues in operating systems.  Namely:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;If a thread is forced to give up the GIL due to a timeout, it is penalized with lower priority.&lt;/li&gt;
&lt;li&gt;If a thread voluntarily gives up the GIL because it performed I/O, it is reward with higher priority.&lt;/li&gt;
&lt;li&gt;High priority threads always preempty low-priority threads.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;The results of this approach were impressive.   If you run the same tests with priorities on 2 CPU cores, you get this result:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Python 3.2 (New GIL with priorities), 0.298 seconds (35156921.564 bytes/sec)&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;The prioritized GIL also gives good performance for Antoine's own &lt;tt&gt;ccbench.py&lt;/tt&gt; benchmark.&lt;/p&gt;&lt;table border=1 cellspacing=15&gt;&lt;tr&gt;
&lt;th&gt;New GIL&lt;/th&gt;&lt;th&gt;New GIL with priorities&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;br /&gt;
&lt;pre&gt;== CPython 3.2a0.0 (py3k:78250) ==
== i386 Darwin on 'i386' ==

--- Throughput ---

Pi calculation (Python)

threads=1: 873 iterations/s.
threads=2: 845 ( 96 %)
threads=3: 837 ( 95 %)
threads=4: 820 ( 93 %)

regular expression (C)

threads=1: 348 iterations/s.
threads=2: 339 ( 97 %)
threads=3: 328 ( 94 %)
threads=4: 317 ( 91 %)

bz2 compression (C)

threads=1: 367 iterations/s.
threads=2: 655 ( 178 %)
threads=3: 642 ( 174 %)
threads=4: 646 ( 175 %)

--- Latency ---

Background CPU task: Pi calculation (Python)

CPU threads=0: 0 ms. (std dev: 0 ms.)
CPU threads=1: 5 ms. (std dev: 0 ms.)
CPU threads=2: 2 ms. (std dev: 2 ms.)
CPU threads=3: 138 ms. (std dev: 100 ms.)
CPU threads=4: 132 ms. (std dev: 99 ms.)

Background CPU task: regular expression (C)

CPU threads=0: 0 ms. (std dev: 0 ms.)
CPU threads=1: 6 ms. (std dev: 1 ms.)
CPU threads=2: 6 ms. (std dev: 6 ms.)
CPU threads=3: 6 ms. (std dev: 4 ms.)
CPU threads=4: 10 ms. (std dev: 8 ms.)

Background CPU task: bz2 compression (C)

CPU threads=0: 0 ms. (std dev: 0 ms.)
CPU threads=1: 0 ms. (std dev: 1 ms.)
CPU threads=2: 0 ms. (std dev: 0 ms.)
CPU threads=3: 0 ms. (std dev: 0 ms.)
CPU threads=4: 0 ms. (std dev: 0 ms.)

&lt;/pre&gt;&lt;/td&gt;
&lt;td&gt;&lt;br /&gt;
&lt;pre&gt;== CPython 3.2a0.0 (py3k:78215M) ==
== i386 Darwin on 'i386' ==

--- Throughput ---

Pi calculation (Python)

threads=1: 885 iterations/s.
threads=2: 860 ( 97 %)
threads=3: 869 ( 98 %)
threads=4: 859 ( 97 %)

regular expression (C)

threads=1: 362 iterations/s.
threads=2: 358 ( 98 %)
threads=3: 349 ( 96 %)
threads=4: 354 ( 97 %)

bz2 compression (C)

threads=1: 373 iterations/s.
threads=2: 654 ( 175 %)
threads=3: 649 ( 173 %)
threads=4: 638 ( 170 %)

--- Latency ---

Background CPU task: Pi calculation (Python)

CPU threads=0: 0 ms. (std dev: 0 ms.)
CPU threads=1: 0 ms. (std dev: 0 ms.)
CPU threads=2: 0 ms. (std dev: 2 ms.)
CPU threads=3: 0 ms. (std dev: 1 ms.)
CPU threads=4: 0 ms. (std dev: 1 ms.)

Background CPU task: regular expression (C)

CPU threads=0: 0 ms. (std dev: 0 ms.)
CPU threads=1: 2 ms. (std dev: 1 ms.)
CPU threads=2: 3 ms. (std dev: 3 ms.)
CPU threads=3: 2 ms. (std dev: 1 ms.)
CPU threads=4: 2 ms. (std dev: 2 ms.)

Background CPU task: bz2 compression (C)

CPU threads=0: 0 ms. (std dev: 0 ms.)
CPU threads=1: 0 ms. (std dev: 1 ms.)
CPU threads=2: 0 ms. (std dev: 1 ms.)
CPU threads=3: 0 ms. (std dev: 1 ms.)
CPU threads=4: 0 ms. (std dev: 1 ms.)

&lt;/pre&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;&lt;p&gt;The overall outcome of the GIL open space was that having a priority mechanism was probably a good idea.  However, a lot of people wanted to study the problem in more detail and to think about different possible implementations.   I am posting the following tar file that has my own modifications to the GIL used for the above benchmarks:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.dabeaz.com/python/prioritygil.tar.gz"&gt;prioritygil.tar.gz&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;P&gt;Note: This tar file has all of the modified files in the Python 3.2 source (pystate.h, pystate.c, and ceval_gil.h) along with the io testing benchmark.    Be advised that this patch is only intended for further study by others---it's kind of hacked together and really only a proof of concept implementation of one possible priority scheme.   A real implementation would still need to address some issues not covered in my patch (e.g., starvation effects).&lt;/p&gt;&lt;p&gt;Due to other time commitments, I'm not going to be able to do much followup with this patch at this moment.  However, I do want to encourage others to at least consider the benefit of introducing thread priorities and to explore different possible implementations.  Initial results seem to indicate that this can fix the GIL for both CPU-bound threads and for&lt;br /&gt;
I/O performance.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-8935811723349955313?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2010/02/revisiting-thread-priorities-and-new.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>3</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-4485812278500153019</guid><pubDate>Wed, 03 Feb 2010 02:12:00 +0000</pubDate><atom:updated>2010-02-02T18:12:46.189-08:00</atom:updated><title>A function that works as a context manager and a decorator</title><description>&lt;p&gt;As a followup to my last blog post on timings, I present the following function which works as both a decorator and a context manager.&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;# timethis.py
import time
from contextlib import contextmanager

def timethis(what):
    @contextmanager
    def benchmark():
        start = time.time()
        yield
        end = time.time()
        print("%s : %0.3f seconds" % (what, end-start))
    if hasattr(what,"__call__"):
        def timed(*args,**kwargs):
            with benchmark():
                return what(*args,**kwargs)
        return timed
    else:
        return benchmark()
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Here is a short demonstration of how it works:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;# Usage as a context manager
with timethis("iterate by lines (UTF-8)"):
     for line in open("biglog.txt",encoding='utf-8'):
          pass

# Usage as a decorator
@timethis
def iterate_by_lines_latin_1():
    for line in open("biglog.txt",encoding='latin-1'):
        pass

iterate_by_lines_latin_1()
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;If you run it, you'll get output like this:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;bash % &lt;b&gt;python3 timethis.py&lt;/b&gt;
iterate by lines (UTF-8) : 3.762 seconds
&amp;lt;function iterate_by_lines_latin_1 at 0x100537958&gt; : 3.513 seconds
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Naturally, this bit of code would be a good thing to bring into your next code review just to make sure people are actually paying attention.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-4485812278500153019?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2010/02/function-that-works-as-context-manager.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>4</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-8673452591802794299</guid><pubDate>Tue, 02 Feb 2010 13:01:00 +0000</pubDate><atom:updated>2010-02-02T05:01:46.052-08:00</atom:updated><title>A Context Manager for Timing Benchmarks</title><description>&lt;p&gt;I spend a lot of time studying different aspects of Python, different implementation techniques, and so forth.  As part of that, I often carry out little performance benchmarks.  For small things, I'll often use the &lt;a href="http://docs.python.org/library/timeit"&gt;timeit&lt;/a&gt; module.  For example:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;from timeit import timeit&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;timeit("math.sin(2)","import math")&lt;/b&gt;
0.29826998710632324
&gt;&gt;&gt; &lt;b&gt;timeit("sin(2)","from math import sin")&lt;/b&gt;
0.21983098983764648
&gt;&gt;&gt;
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;However, for larger blocks of code, I tend to just use the &lt;tt&gt;time&lt;/tt&gt; module directly like this:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;import time
start = time.time()
...
... &lt;em&gt;some big calculation&lt;/em&gt;
...
end = time.time()
print("Whatever : %0.3f seconds" % (end-start))
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Having typed that particular code template more often than I care to admit, it occurred to me that I really ought to just make a context manager for doing it.  Something like this:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;# benchmark.py
import time
class benchmark(object):
    def __init__(self,name):
        self.name = name
    def __enter__(self):
        self.start = time.time()
    def __exit__(self,ty,val,tb):
        end = time.time()
        print("%s : %0.3f seconds" % (self.name, end-self.start))
        return False
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Now, I can just use that context manager whenever I want to do that kind of timing benchmark.  For example:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;# fileitertest.py
from benchmark import benchmark

with benchmark("iterate by lines (UTF-8)"):
     for line in open("biglog.txt",encoding='utf-8'):
          pass

with benchmark("iterate by lines (Latin-1)"):
     for line in open("biglog.txt",encoding='latin-1'):
         pass

with benchmark("iterate by lines (Binary)"):
     for line in open("biglog.txt","rb"):
         pass
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;If you run it, you might get output like this:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;bash % &lt;b&gt;python3 fileitertest.py&lt;/b&gt;
iterate by lines (UTF-8) : 3.903 seconds
iterate by lines (Latin-1) : 3.615 seconds
iterate by lines (Binary) : 1.886 seconds
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Nice. I like it already!&lt;br /&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-8673452591802794299?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2010/02/context-manager-for-timing-benchmarks.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>7</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-2082133620075059646</guid><pubDate>Fri, 29 Jan 2010 03:02:00 +0000</pubDate><atom:updated>2010-01-29T03:19:04.135-08:00</atom:updated><title>A few useful bytearray tricks</title><description>&lt;p&gt;When I first saw the new Python 3 &lt;tt&gt;bytearray&lt;/tt&gt; object (also back-ported to Python 2.6), I wasn't exactly sure what to make of it.  On the surface, it seemed like a kind of mutable 8-bit string (a feature sometimes requested by users of Python 2).  For example:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;s = bytearray(b"Hello World")&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;s[:5] = b"Cruel"&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;s&lt;/b&gt;
bytearray(b'Cruel World')
&gt;&gt;&gt;
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;On the other hand, there are aspects of &lt;tt&gt;bytearray&lt;/tt&gt; objects that are completely unlike a string.  For example, if you iterate over a bytearray, you get integer byte values:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;s = bytearray(b"Hello World")&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;for c in s: print(c)&lt;/b&gt;
...
72
101
108
108
111
32
87
111
114
108
100
&gt;&gt;&gt; 
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Similarly, indexing operations are tied to integers:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;s[1]&lt;/b&gt;
101
&gt;&gt;&gt; &lt;b&gt;s[1] = 97&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;s[1] = b'a'&lt;/b&gt;
Traceback (most recent call last):
  File "&lt;stdin&gt;", line 1, in &lt;module&gt;
TypeError: an integer is required
&gt;&gt;&gt; 
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Finally, there's the fact &lt;tt&gt;bytearray&lt;/tt&gt; instances have most of the methods associated with strings as well as methods associated with lists.  For example:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;s.split()&lt;/b&gt;
[bytearray(b'Hello'), bytearray(b'World')]
&gt;&gt;&gt; &lt;b&gt;s.append(33)&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;s&lt;/b&gt;
bytearray(b'Hello World!')
&gt;&gt;&gt;
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Although documentation on bytearrays describes these features, it is a little light on meaningful use cases.  Needless to say, if you have too much spare time (sic) on your hands, this is the kind of thing that you start to think about.  So, I thought I'd share three practical uses of bytearrays.&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;&lt;b&gt;Example 1: Assembling a message from fragments&lt;/b&gt;&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;Suppose you're writing some network code that is receiving a large message on a socket connection.  If you know about sockets, you know that the &lt;tt&gt;recv()&lt;/tt&gt; operation doesn't wait for all of the data to arrive.  Instead, it merely returns what's currently available in the system buffers.  Therefore, to get all of the data, you might write code that looks like this:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;# remaining = number of bytes being received (determined already)
msg = b""
while remaining &gt; 0:
    chunk = s.recv(remaining)    # Get available data
    msg += chunk                 # Add it to the message
    remaining -= len(chunk)  
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;The only problem with this code is that concatenation (+=) has horrible performance.  Therefore, a common performance optimization in Python 2 is to collect all of the chunks in a list and perform a join when you're done.  Like this:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;# remaining = number of bytes being received (determined already)
msgparts = []
while remaining &gt; 0:
    chunk = s.recv(remaining)    # Get available data
    msgparts.append(chunk)       # Add it to list of chunks
    remaining -= len(chunk)  
msg = b"".join(msgparts)          # Make the final message
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Now, here's a third solution using a &lt;tt&gt;bytearray&lt;/tt&gt;:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;# remaining = number of bytes being received (determined already)
msg = bytearray()
while remaining &gt; 0:
    chunk = s.recv(remaining)    # Get available data
    msg.extend(chunk)            # Add to message
    remaining -= len(chunk)  
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Notice how the bytearray version is really clean.  You don't collect parts in a list and you don't perform that cryptic join at the end. Nice.&lt;/p&gt;&lt;p&gt;Of course, the big question is whether or not it performs.  To test this out, I first made a list of small byte fragments like this:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;chunks = [b"x"*16]*512
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;I then used the &lt;tt&gt;timeit&lt;/tt&gt; module to compare the following two code fragments:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;# Version 1
msgparts = []
for chunk in chunks:
    msgparts.append(chunk)
msg = b"".join(msgparts)

# Version 2
msg = bytearray()
for chunk in chunks:
    msg.extend(chunk)
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;When tested, version 1 of the code ran in 99.8s whereas version 2 ran in 116.6s (a version using += concatenation takes 230.3s by comparison).   So while performing a join operation is still faster, it's only faster by about 16%.   Personally, I think the cleaner programming of the bytearray version might make up for it.&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;&lt;b&gt;Example 2: Binary record packing&lt;/b&gt;&lt;/p&gt;&lt;p&gt;This example is an slight twist on the last example.  Support you had a large Python list of integer (x,y) coordinates.  Something like this:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;points = [(1,2),(3,4),(9,10),(23,14),(50,90),...]
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Now, suppose you need to write that data out as a binary encoded file consisting of a 32-bit integer length followed by each point packed into a pair of 32-bit integers.  One way to do it would be to use the &lt;tt&gt;struct&lt;/tt&gt; module like this:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;import struct
f = open("points.bin","wb")
f.write(struct.pack("I",len(points)))
for x,y in points:
    f.write(struct.pack("II",x,y))
f.close()
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;The only problem with this code is that it performs a large number of small &lt;tt&gt;write()&lt;/tt&gt; operations.  An alternative approach is to pack everything into a bytearray and only perform one write at the end.  For example:&lt;br /&gt;
&lt;blockquote&gt;&lt;pre&gt;import struct
f = open("points.bin","wb")
msg = bytearray()
msg.extend(struct.pack("I",len(points))
for x,y in points:
    msg.extend(struct.pack("II",x,y))
f.write(msg)
f.close()
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Sure enough, the version that uses &lt;tt&gt;bytearray&lt;/tt&gt; runs much faster.  In a simple timing test involving a list of 100000 points, it runs in about half the time as the version that makes a lot of small writes.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Example 3:&lt;/b&gt; Mathematical processing of byte values&lt;/p&gt;&lt;p&gt;The fact that bytearrays present themselves as arrays of integers makes it easier to perform certain kinds of calculations.   In a recent embedded systems project, I was using Python to communicate with a device over a serial port.  As part of the communications protocol, all messages had to be signed with a Longitudinal Redundancy Check (LRC) byte.  An LRC is computed by taking an XOR across all of the byte values.  &lt;br /&gt;
&lt;/p&gt;&lt;p&gt;Bytearrays make such calculations easy.  Here's one version:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;message = bytearray(...)     # Message already created
lrc = 0
for b in message:
    lrc ^= b
message.append(lrc)          # Add to the end of the message
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Here's a version that increases your job security:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;message.append(functools.reduce(lambda x,y:x^y,message))
&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;And here's the same calculation in Python 2 without bytearrays:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;message = "..."       # Message already created
lrc = 0
for b in message:
    lrc ^= ord(b)
message += chr(lrc)        # Add the LRC byte
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Personally, I like the bytearray version.  There's no need to use &lt;tt&gt;ord()&lt;/tt&gt; and you can just append the result at the end of the message instead of using concatenation.&lt;/p&gt;&lt;p&gt;Here's another cute example.  Suppose you wanted to run a bytearray through a simple XOR-cipher.  Here's a one-liner to do it:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;key = 37&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;message = bytearray(b"Hello World")&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;s = bytearray(x ^ key for x in message)&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;s&lt;/b&gt;
bytearray(b'm@IIJ\x05rJWIA')
&gt;&gt;&gt; &lt;b&gt;bytearray(x ^ key for x in s)&lt;/b&gt;
bytearray(b"Hello World")
&gt;&gt;&gt; 
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;b&gt;Final Comments&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Although some programmers might focus on bytearrays as a kind of mutable string, I find their use as an efficient means for assembling messages from fragments to be much more interesting.  That's because this kind of problem comes up a lot in the context of interprocess communication, networking, distributed computing, and other related areas.  Thus, if you know about bytearrays, it might lead to code that has good performance and is easy to understand. &lt;/p&gt;&lt;p&gt;That's it for this installment.  In case you're wondering, this topic is also related to my upcoming PyCON'2010 tutorial "Mastering Python 3 I/O."&lt;br /&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-2082133620075059646?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2010/01/few-useful-bytearray-tricks.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>13</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-5519571958197782221</guid><pubDate>Wed, 27 Jan 2010 04:36:00 +0000</pubDate><atom:updated>2010-01-28T07:12:58.615-08:00</atom:updated><title>Reexamining Python 3 Text I/O</title><description>&lt;p&gt;&lt;b&gt;Note:&lt;/b&gt; Since I first posted this, I added a performance test using the Python 2.6.4 codecs module.  This addition is highlighted in &lt;font color="#ff0000"&gt;red&lt;/font&gt;.&lt;/p&gt;&lt;p&gt;When Python 3.0 was first released, I tried it out on a few things and walked away unimpressed.   By far, the big negative was the horrible I/O performance.  For instance, scripts to perform simple data analysis tasks like processing a web server log file were running more than 30 times slower than Python 2.  Even though there were many new features of Python 3 to be excited about, the I/O performance alone was enough to make me not want to use it---or recommend it to anyone else for that matter.&lt;/p&gt;&lt;p&gt;Some time has passed since then.  For example, Python-3.1.1 is out and many improvements have been made.  To force myself to better understand the new Python 3 I/O system, I've been working on a tutorial &lt;a href="http://us.pycon.org/2010/tutorials/beazley_python3/"&gt;Mastering Python 3 I/O&lt;/a&gt; for the upcoming PyCON'2010 conference in Atlanta. Overall, I have to say that I'm pretty impressed with what I've found--and not just in terms of improved performance.&lt;/p&gt;&lt;p&gt;Due to space constraints, I can't talk about everything in my tutorial here.  However, I thought I would share some thoughts about text-based I/O in Python 3.1 and discuss a few examples.  Just as a disclaimer, I show a few benchmarks, but my intent is not to do a full study of every possible aspect of text I/O handling.  I would strongly advise you to download Python 3.1.1 and perform your own tests to get a better feel for it.&lt;/p&gt;&lt;p&gt;Like many people, one of my main uses of Python is data processing and parsing.  For example, consider the contents of a typical Apache web server log:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;75.54.118.139 - - [24/Feb/2008:00:15:42 -0600] "GET /favicon.ico HTTP/1.1" 404 133
75.54.118.139 - - [24/Feb/2008:00:15:49 -0600] "GET /software.html HTTP/1.1" 200 3163
75.54.118.139 - - [24/Feb/2008:00:16:10 -0600] "GET /ply/index.html HTTP/1.1" 200 8018
213.145.165.82 - - [24/Feb/2008:00:16:19 -0600] "GET /ply/ HTTP/1.1" 200 8018
...
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Let's look at a simple script that processes this file.  For example, suppose you wanted to produce a list of all URLs that have generated a 404 error.   Here's a really simple (albeit hacky) script that does that:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;error_404_urls = set()
for line in open("access-log"):
    fields = line.split()
    if fields[-2] == '404':
        error_404_urls.add(fields[-4])

for name in error_404_urls:
    print(name)
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;On my machine, I have a 325MB log file consisting of 3649000 lines--a perfect candidate for performing a few benchmarks.   Here are the numbers that you get running the above script with different Python versions.  UCS-2 refers to Python compiled with 16-bit Unicode characters.  UCS-4 refers to Python compiled with 32-bit Unicode characters (the &lt;tt&gt;--with-wide-unicode&lt;/tt&gt; configuration option).  Also, in the interest of full disclosure, these tests were performed with a warm disk cache on a 2 GHZ Intel Core 2 Duo Apple Macbook with 4GB of memory under OS-X 10.6.2 (Snow Leopard).&lt;/p&gt;&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;th align="center"&gt;Python Version&lt;/th&gt;&lt;th align="center"&gt;Time (seconds)&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align="center"&gt;2.6.4&lt;/td&gt;&lt;td align="center"&gt;7.91s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align="center"&gt;3.0&lt;/td&gt;&lt;td align="center"&gt;125.42s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align="center"&gt;3.1.1 (UCS-2)&lt;/td&gt;&lt;td align="center"&gt;14.11s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align="center"&gt;3.1.1 (UCs-4)&lt;/td&gt;&lt;td align="center"&gt;17.32s&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;&lt;/blockquote&gt;&lt;p&gt;As you can see, Python 3.0 performance was an anomaly--the performance of Python 3.1.1 is substantially better.    To better understand the I/O component of this script, I ran a modified test with the following code&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;for line in open("access-log"):
    pass
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Here are the performance results for iterating over the file by lines:&lt;/p&gt;&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;th align="center"&gt;Python Version&lt;/th&gt;&lt;th align="center"&gt;Time (seconds)&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align="center"&gt;2.6.4&lt;/td&gt;&lt;td align="center"&gt;1.50s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align="center"&gt;&lt;font color="#ff0000"&gt;2.6.4 (codecs, UTF-8)&lt;/font&gt;&lt;/td&gt;&lt;td align="center"&gt;&lt;font color="#ff0000"&gt;52.22s&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align="center"&gt;3.0&lt;/td&gt;&lt;td align="center"&gt;105.87s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align="center"&gt;3.1.1 (UCS-2)&lt;/td&gt;&lt;td align="center"&gt;4.35s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align="center"&gt;3.1.1 (UCs-4)&lt;/td&gt;&lt;td align="center"&gt;6.11s&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;&lt;/blockquote&gt;&lt;p&gt;If you look at these numbers, you will see that the I/O performance of Python 3.1 has improved substantially. &lt;font color="#ff0000"&gt;It is also substantially faster than using the codecs module in Python 2.6.&lt;/font&gt;  However, you'll also observe that the performance is still quite a bit worse than the native Python 2.6 file object.  For example, in the table, iterating over lines is about 3x slower in Python 3.1.1 (UCS-2).  How can that be good?  That's 300% slower!&lt;/p&gt;&lt;p&gt;Let's talk about the numbers in more detail.  The decreased performance in Python 3 is almost solely due to the overhead of the underlying Unicode conversion applied to text input.  That conversion process involves two distinct steps:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Input data (bytes) has to be scanned and characters decoded according to some encoding (UTF-8 by default).&lt;/li&gt;
&lt;li&gt;The decoded character data has to be stored as an array of multibyte integers that represent the associated string result.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;The overhead of decoding is a direct function of how complicated the underlying codec is.  Although UTF-8 is relatively simple, it's still more complex than an encoding such as Latin-1.  Let's see what happens if we try reading the file with "latin-1" encoding instead. Here's the modified test code:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;for line in open("access-log",encoding='latin-1'):
    pass
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Here are the modified performance results that show an improvement:&lt;/p&gt;&lt;blockquote&gt;&lt;table&gt;&lt;tr&gt;&lt;th align="center"&gt;Python Version&lt;/th&gt;&lt;th align="center"&gt;Time (seconds)&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align="center"&gt;3.1.1 (UCS-2)&lt;/td&gt;&lt;td align="center"&gt;3.64s (was 4.35s)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align="center"&gt;3.1.1 (UCs-4)&lt;/td&gt;&lt;td align="center"&gt;5.33s (was 6.11s)&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;&lt;/blockquote&gt;&lt;p&gt;Lesson learned : The encoding matters.  So, if you're working purely with ASCII text, specifying an encoding such as 'latin-1' will speed everything up.  Just so you know, if you specify 'ascii' encoding, you get no improvement over UTF-8.   This is because 'ascii' requires more work to decode than 'latin-1' (due to an extra check for bytes outside the range 0-127 in the decoding process).&lt;/p&gt;&lt;p&gt;At this point, you're still saying that it's slower.  Yes, even with a faster encoding, Python 3.1.1 is still about 2.5x slower than Python 2.6.4 on this simple I/O test.   Is there anything that can be done about that?&lt;/p&gt;&lt;p&gt;The short answer is "not really."  Since Python 3 strings are Unicode, the process of reading a simple 8-bit text file is always going to involve an extra process of converting and copying the byte-oriented data into the multibyte Unicode representation.  Just to give you an idea, let's drop into C programming and consider the following program:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;#include &amp;lt;stdio.h&amp;gt;

int main() {
  FILE *f;
  char  bytes[256];

  f = fopen("access-log","r");
  while (fgets(bytes,256,f)) {  // Yes, hacky 
  }
}
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;This program does nothing more than iterate over lines of a file--think of it as the ultimate stripped down version of our Python-2.6.4 test.  If you run it, takes 1.13s to run on the same log file used for our earlier Python tests.&lt;/p&gt;&lt;p&gt;When you go to Python 3, there is always extra conversion.   It's like modifying the C program as follows:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;#include &amp;lt;stdio.h&amp;gt;

int main() {
  FILE *f;
  char  bytes[256], *c;
  short  unicode[256], *u;

  f = fopen("biglog.txt","r");
  while (fgets(bytes,256,f)) {
    c = bytes;
    u = unicode;
    while (*c) {    /* Convert to Unicode */
      *(u++) = (short) *(c++);
    }
  }
}
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Sure enough, if you run this modified C program, it takes about 1.7 seconds--a nearly 50% performance hit just from that extra copying and conversion step.  Minimally, Python 3 has to do the same conversion.  However, it's also performing dynamic memory allocation, reference counting, and other low-level operations.  So, if you factor all of that in, the performance numbers start to make a little more sense. You also start to understand why it might be really hard to do much better.&lt;/p&gt;&lt;p&gt;Now, should you care about all of this?   Truthfully, most programs are probably not going to be affected by degraded text I/O performance as much as you think.  That's because most interesting programs do far more than just I/O.   Go back and consider the original script that I presented.  On Python-2.6.4, it took 7.91s to execute.   If I go back and tune the script to use the more efficient 'latin-1' encoding, it takes 13.8s with Python-3.1.1.  Yes, that's about 1.75x slower than before. However, the key point is that it's not 2.5x slower as our earlier I/O tests would suggest.   The performance impact will become less and less as the script performs more non-IO related work.&lt;/p&gt;&lt;p&gt;Finally, let's say that you still can't live with the performance degradation.  If you're just working with simple ASCII data files, you might solve this problem by turning to binary I/O instead.  For example, the following script variant uses binary I/O and bytes for most of its processing--only converting text to Unicode when absolutely necessary for printing.&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;error_404_urls = set()
for line in open("access-log","rb"):
    fields = line.split()
    if fields[-2] == b'404':
        error_404_urls.add(fields[-4])

for name in error_404_urls:
    print(name.decode('latin-1'))
&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;p&gt;If you run this final script, you find that it takes 8.22s in Python 3.1.1--which is only about 4% slower than the Python-2.6.4.  How about that!&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;The bottom line is that Python-3.1 is definitely worth a second look--especially if you tried the earlier Python 3.0 release and were disappointed with its performance.  Although text-based I/O is always going to be slower in Python 3 due to extra Unicode processing, it might not matter as much in practice.  Plus, binary I/O in Python 3 is still quite fast which means that you can turn to it as a last resort.&lt;/p&gt;&lt;p&gt;If you want to know more, attend my &lt;a href="http://us.pycon.org/2010/tutorials/beazley_python3/"&gt;Mastering Python 3 I/O&lt;/a&gt; at PyCON'2010 or sign up for the &lt;a href="http://www.dabeaz.com/chicago/index.html"&gt;Special Preview&lt;/a&gt; in Chicago.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Final Notes:&lt;/b&gt;&lt;br /&gt;
&lt;/p&gt;&lt;ul&gt;&lt;li&gt;All versions of Python were compiled from source using the exact same configuration, compiler, and environment settings.&lt;/li&gt;
&lt;li&gt;Python timing tests were performed using the &lt;tt&gt;time&lt;/tt&gt; module and enclosing code with these statements:&lt;br /&gt;
&lt;pre&gt;import time
start = time.time()
... statements ...
end = time.time()
print(end-start)
&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-5519571958197782221?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2010/01/reexamining-python-3-text-io.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>7</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-1344512631157130266</guid><pubDate>Thu, 21 Jan 2010 18:00:00 +0000</pubDate><atom:updated>2010-01-21T10:00:47.803-08:00</atom:updated><title>Slashdot, Pronouns, and the Python Essential Reference</title><description>Yesterday, I was ecstatic to see a &lt;a href="http://books.slashdot.org/story/10/01/20/1431242/Python-Essential-Reference-4th-Ed"&gt;positive review&lt;/a&gt; of my &lt;a href="http://www.amazon.com/python-Essential-Reference-David-Beazley/dp/0672329786/"&gt;Python Essential Reference&lt;/a&gt; book on Slashdot.  I've never had a book reviewed on Slashdot before.  However, I also know that with Slashdot, one never really knows what direction the subsequent discussion is going to take.  For instance, will someone jump in and say something like "in Soviet Russia, Python indents you" or will the conversation devolve into something about how Python programmers will never have a girlfriend?  That's not true by the way. I once had a girlfriend who went to hear me talk for 90 minutes about LALR(1) parser generators at a Chipy meeting despite the fact that she didn't know the first thing about programming.   That's surely a sign of true love or insanity if there ever was one.  Needless to say, I married her. However, I digress.&lt;br /&gt;
 &lt;br /&gt;
No, this time around, the Slashdot discussion decided it was going to focus on the use of pronouns--namely in response to a comment that included the sentence "... there is a lot of what a developer needs and very little of what she doesn't need."   Now, I am by no means any fan of political correctness, but I had to chuckle at the irony.  Of all of the things to discuss about the Python Essential Reference, pronouns would have to rank at about the bottom of the list.  This is because the entire book is virtually devoid of personal pronouns.  With the exception of the word "you" (e.g., "you type this..."), you won't find "he", "she", "him", "her", "we", or anything like that used anywhere in the text.   This was an intentional choice, but it wasn't related to any kind of political influence (in fact, editors of the Essential Reference have often tried to add pronouns like "he" and "she" to the text only to have me take them out again).  &lt;br /&gt;
&lt;br /&gt;
First published in 1999, the Essential Reference was actually my second major writing project--the first being my Ph.D. dissertation which had been completed the year before.  As you know, writing a dissertation is a pretty major affair.  Not only do you have to do original research and defend it, you also have to write a major document describing the results.  For a typical graduate student, the dissertation is the most technically demanding document you will ever write.  It might even be the first document that you will ever submit to a real-world copy editor--an editor who will very likely tear your precious document to shreds in front of your eyes.&lt;br /&gt;
&lt;br /&gt;
In my case, the final stage of my dissertation involved a somewhat prolonged battle with the dissertation editor at the University of Utah.   Upon submitting the document, she would immediately put it under the microscope to see if it met the required "technical specifications."  This meant measuring margins, line spacing, tables, figures, and other details with a ruler.  Any deviation whatsoever meant instant rejection of the entire document--please play again.  &lt;br /&gt;
&lt;br /&gt;
Assuming one could pass the basic technical requirements, the next stage involved a review to see if you were strictly adhering to the required writing "style guide."  When submitting a dissertation, you actually had to indicate a specific writing style guide.  For example, I said that I was writing the document according to the "Chicago Manual of Style."  What this meant in practical terms is that upon submitting the dissertation to the editor, she would read it and return it to you a few days later dripping in a sea of red ink.  Every sentence of the document that did not precisely adhere to that style guide would be torn apart.  I have to say that in my entire academic and professional career (grade school, high school, college, etc.), I have never had any paper reviewed like that. &lt;br /&gt;
&lt;br /&gt;
Just to give you an example of the agony, if I wrote something like "the data is plotted" (something that sounded perfectly reasonable to me as a programmer) the editor would reject it because "data" is a plural (of datum) and you can't use "is" with a plural (e.g., you would never say "the points is plotted.").   The other major source of agony was in the use of pronouns.  The editor would instantly punish you for any use of a personal pronoun.  So, a sentence like "we took the points and processed them with a script" would be rejected. &lt;br /&gt;
&lt;br /&gt;
Essentially the editor wanted the entire document to be written in what I would roughly describe as "academic passive voice."    It's a style of writing where you never identify who is actually carrying out various actions.  So, instead of saying "we took the points and processed them with a script" you had to write "the points were processed with a script."  As you can see, A major feature of this writing style is that it is very direct and precise.  Not only is the second sentence more compact, it doesn't muddle the discussion with unimportant details about who is actually carrying out the action.  Obviously, you also avoid the whole issue of "he" versus "she" with such a writing style.&lt;br /&gt;
&lt;br /&gt;
Anyways, work on the Python Essential Reference started just 6 months after finishing my dissertation.   Having fought all of those editor battles, I wrote it in the exact same style.  So far as I can remember, I don't think any pronoun other than "it" or "its" appeared in the text.   It must have blown the copy editor's mind.  What kind of deranged lunatic would write a 300-page impersonal document like that?  Especially since writing in the passive voice is something so actively discouraged.&lt;br /&gt;
&lt;br /&gt;
Over the last ten years, various copy-editors have worked on the Essential Reference, but much of that original academic writing style remains.  At some point, use of the word "you" was introduced in the book.  I was somewhat lukewarm about it at the time, but as an author you also learn to pick and choose your battles--and that wasn't one that seemed worth fighting (unlike the battle to convince my publisher that putting out a Python 2.6 book hot on the heels of Python 3.0 was going to make any sense).   &lt;br /&gt;
&lt;br /&gt;
So there you have it.   A review of a book virtually devoid of personal pronouns spawns a big discussion on the use of he/she on Slashdot.   Who would have thought?&lt;br /&gt;
&lt;br /&gt;
Naturally, I disavow any grammatical mistakes in this blog post---after all, I don't have a editor.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-1344512631157130266?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2010/01/slashdot-pronouns-and-python-essential.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>4</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-4565997516889751136</guid><pubDate>Sun, 17 Jan 2010 18:09:00 +0000</pubDate><atom:updated>2010-01-17T10:09:24.408-08:00</atom:updated><title>Presentation on the new Python GIL</title><description>For anyone who missed it, I gave a presentation on the new Python GIL, implemented by Antoine Pitrou, at the January 14, 2010 meeting of Chipy.   The presentation slides can be found at &lt;a href="http://www.dabeaz.com/python/NewGIL.pdf"&gt;http://www.dabeaz.com/python/NewGIL.pdf&lt;/a&gt;.  I don't have any followup comments to put here at this time.  However, I think this is an exciting new development for Python 3.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-4565997516889751136?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2010/01/presentation-on-new-python-gil.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>19</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-4523567443307976608</guid><pubDate>Tue, 05 Jan 2010 14:18:00 +0000</pubDate><atom:updated>2010-01-06T05:09:58.381-08:00</atom:updated><title>The Python GIL Visualized</title><description>&lt;p&gt;In preparation for my upcoming PyCON'2010 talk on "Understanding the Python GIL", I've been working on a variety of new material--including some graphical visualization of the GIL behavior described in my earlier &lt;a href="http://www.dabeaz.com/python/GIL.pdf"&gt;talk&lt;/a&gt;.   I'm still experimenting, but check it out.&lt;/p&gt;&lt;p&gt;In these graphs, Python interpreter ticks are shown along the X-axis.   The two bars indicate two different threads that are executing.  White regions indicate times at which a thread is completely idle.  Green regions indicate when a thread holds the GIL and is running.  Red regions indicate when a thread has been scheduled by the operating system only to awake and find that the GIL is not available (e.g., the infamous "GIL Battle").  For those who don't want to read, here is the legend again in pictures:&lt;/p&gt;&lt;p&gt;&lt;center&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/GILLegend.png"&gt;&lt;/center&gt;&lt;/p&gt;&lt;p&gt;Okay, now let's look at some threads.   First, here is the behavior of running two CPU-bound threads on a single CPU system.  As you will observe, the threads nicely alternate with each other after long periods of computation.&lt;/p&gt;&lt;p&gt;&lt;center&gt;&lt;img src="http://www.dabeaz.com/images/GIL_1cpu.png"&gt;&lt;/center&gt;&lt;/p&gt;&lt;p&gt;Now, let's go fire up the code on your fancy new dual-core laptop.  Yow! Look at all of that GIL contention.  Again, all of those red regions indicate times where the operating system has scheduled a Python thread on one of the cores, but it can't run because the thread on the other core is holding it.&lt;/p&gt;&lt;p&gt;&lt;center&gt;&lt;img src="http://www.dabeaz.com/images/GIL_2cpu.png"&gt;&lt;/center&gt;&lt;/p&gt;&lt;p&gt;Here's an interesting case that involves an I/O bound thread competing with a CPU-bound thread.  In this example, the I/O thread merely echoes UDP packets.  Here is the code for that thread.&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;def thread_1(port):
    s = socket(AF_INET,SOCK_DGRAM)
    s.bind(("",port))
    while True:
        msg, addr = s.recvfrom(1024)
        s.sendto(msg,addr)
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;The other thread (thread 2) is just mindlessly spinning.  This graph shows what happens when you send a UDP message to thread 1.&lt;/p&gt;&lt;p&gt;&lt;center&gt;&lt;img src="http://www.dabeaz.com/images/GIL_io.png"&gt;&lt;/center&gt;&lt;/p&gt;&lt;p&gt;As you would expect, most of the time is spent running the CPU-bound thread.  However, when I/O is received, there is a flurry of activity that takes place in the I/O thread.  Let's zoom in on that region and see what's happening.&lt;/p&gt;&lt;p&gt;&lt;center&gt;&lt;img src="http://www.dabeaz.com/images/GIL_ioclose.png"&gt;&lt;/center&gt;&lt;/p&gt;&lt;p&gt;In this graph, you're seeing how difficult it is for the I/O bound to get the GIL in order to perform its small amount of processing.  For instance, approximately 17000 interpreter ticks pass between the arrival of the UDP message and successful return of the &lt;tt&gt;s.recvfrom()&lt;/tt&gt; call (and notice all of the GIL contention).  More that 34000 ticks pass between the execution of &lt;tt&gt;s.sendto()&lt;/tt&gt; and looping back to the next &lt;tt&gt;s.recvfrom()&lt;/tt&gt; call.  Needless to say, this is not the behavior you usually want for I/O bound processing. &lt;/p&gt;&lt;p&gt;Anyways, that is all for now.  Come to my PyCON talk to see much more.  Also check out Antoine Pitrou's work on a &lt;a href="http://mail.python.org/pipermail/python-dev/2009-October/093321.html"&gt;new GIL&lt;/a&gt;. &lt;/p&gt;&lt;p&gt;Note: It is not too late to sign up for my &lt;a href="http://www.dabeaz.com/chicago/concurrent.html"&gt;Concurrency Workshop&lt;/a&gt; next week (Jan 14-15). &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-4523567443307976608?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2010/01/python-gil-visualized.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>17</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-8547467207931590304</guid><pubDate>Mon, 14 Dec 2009 11:50:00 +0000</pubDate><atom:updated>2009-12-14T03:50:00.426-08:00</atom:updated><title>Python Concurrency Workshop (Reprise)</title><description>&lt;p&gt;Well, the winter months are now upon us--making it a perfect time to come to Chicago in the middle of January and have your brain exploded by the second edition of my &lt;a href="http://www.dabeaz.com/chicago/index.html"&gt;Python Concurrency Workshop&lt;/a&gt; (January 14-15, 2010).  Over the last few months, I've been working on numerous refinements to the previous workshop and adding some new material related to distributed computing (Actors, REST, distributed objects, etc.).  I think I'm even more excited by this version than the last.&lt;/p&gt;&lt;p&gt;So what is this concurrency workshop you ask?  Well, first all, you may have already encountered a small portion of it if you saw my presentation on the &lt;a href="http://www.dabeaz.com/python/GIL.pdf"&gt;Python GIL&lt;/a&gt;---that was only a small part of the workshop's thread programming section. The rest of the workshop aims to explore a variety of other topics at a similar technical depth.  For example, thread synchronization, thread debugging, message passing, data serialization, interprocess communication, multiprocessing, distributed computing, and advanced I/O handling.  In a nutshell, it's an opportunity to learn more about what makes Python tick and to go beyond what you normally find in the user manual.  The workshop is also a kind of proving ground for some of my future book projects and PyCON tutorials--I have made every effort to keep it cutting edge.&lt;/p&gt;&lt;p&gt;So, you might ask, who is the target audience of the workshop?   Although a lot of advanced material is covered, I think the workshop is best suited for intermediate Python programmers who want to learn more. For instance, the workshop utilizes numerous Python features such as context managers, decorators, generators, and coroutines.  If you've heard of such topics before, but aren't quite sure what they're all about, the workshop will fill in details.   Second, the workshop has a very strong focus on networking and distributed systems.  If you've been doing work in web services, cloud computing, parallel computing, or any related topic, the workshop aims to fill in a variety of essential technical details that will help you write more efficient code.   Finally, if you simply want to escape the office and hang out with other Python hackers, the workshop won't disappoint.&lt;/p&gt;&lt;p&gt;Finally, although there is a small chance the workshop will be held in the middle of a wind-whipped Chicago blizzard, other amenities will more than make up for it. Some of Chicago's finest bakeries and coffee shops surround the workshop venue--ensuring a proper balance of sugar and caffeine required for a workshop of this nature.  You won't be disappointed.&lt;/p&gt;&lt;p&gt;In any case, hopefully I'll see you at the workshop.  It's going to be great!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-8547467207931590304?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2009/12/python-concurrency-workshop-reprise.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-4305260963941133323</guid><pubDate>Fri, 27 Nov 2009 15:50:00 +0000</pubDate><atom:updated>2009-11-27T07:50:12.751-08:00</atom:updated><title>Fun with block towers</title><description>&lt;p&gt;Lately, I've been having a lot of fun playing with wooden blocks--a great toy for toddlers and grown-ups alike.  &lt;/p&gt;&lt;center&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/Blocks/blockbaby.JPG"&gt;&lt;/center&gt;&lt;br /&gt;
&lt;p&gt;There's a certain primal simplicity to blocks.  Sure, you can stack them up in simple towers or piles.  However, my inner geek makes me want to build more tricky structures.  For example, this diamond structure:&lt;p&gt;&lt;center&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/Blocks/diamond.JPG"&gt;&lt;/center&gt;&lt;br /&gt;
&lt;p&gt;Or maybe a diamond with huge spire&lt;/p&gt;&lt;center&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/Blocks/diamond2.JPG"&gt;&lt;/center&gt;&lt;br /&gt;
&lt;p&gt;Or flip the whole thing upside down if you're inclined:&lt;/p&gt;&lt;center&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/Blocks/inverted.JPG"&gt;&lt;/center&gt;&lt;br /&gt;
&lt;p&gt;A more interesting challenge is to build an arch.&lt;/p&gt;&lt;center&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/Blocks/arch.JPG"&gt;&lt;/center&gt;&lt;br /&gt;
&lt;p&gt;And if you can keep that stable, to find out how much you can stack on top of it&lt;/p&gt;&lt;center&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/Blocks/archspire.JPG"&gt;&lt;/center&gt;&lt;br /&gt;
&lt;p&gt;Lately, I've been experimenting with expanding the number of dimensions.  For example, this interesting structure:&lt;/p&gt;&lt;center&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/Blocks/3dsimple.JPG"&gt;&lt;/center&gt;&lt;br /&gt;
&lt;p&gt;Or this more complex extension of the idea&lt;/p&gt;&lt;center&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/Blocks/3dcomplex.JPG"&gt;&lt;/center&gt;&lt;br /&gt;
&lt;p&gt;Somewhere in all of this, there's probably some kind of software development analogy.  Maybe it's the fact that even with simple components, you can make some pretty cool things.  Or maybe it's somehow related to the same inner urge that drives a programmer to build their entire application out of closures, generators, coroutines, actors, tasklets, or something similarly "simple."&lt;/p&gt;&lt;p&gt;Then again, maybe it's more of a warning.  After all, there are those pesky end-users who are going to put their dirty hands on everything when you're done (observe their look of terror).&lt;/p&gt;&lt;center&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/Blocks/enduser2.JPG"&gt;&lt;/center&gt;&lt;br /&gt;
&lt;p&gt;... and well, we all know what happens next.&lt;/p&gt;&lt;p&gt;Anyways, that is all for now.  Hope everyone is enjoying the holiday!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-4305260963941133323?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2009/11/fun-with-block-towers.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>5</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-84152637118440889</guid><pubDate>Fri, 20 Nov 2009 22:50:00 +0000</pubDate><atom:updated>2009-11-20T15:04:52.179-08:00</atom:updated><title>Python Thread Deadlock Avoidance</title><description>&lt;p&gt;One danger of writing programs based on threads is the potential for deadlock--a problem that almost invariably shows up if you happen to write thread code that tries to acquire more than one mutex lock at once.  For example:&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;a_lock = threading.Lock()
b_lock = threading.Lock()

def foo():
    with a_lock:
         ...
         with b_lock:
              # Do something
              ...

t1 = threading.Thread(target=foo)
t1.start()
&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;Code like that looks innocent enough until you realize that some other thread in the system also has a similar idea about locking--but acquires the locks in a slightly different order:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;def bar():
    with b_lock:
         ...
         with a_lock:
              # Do something (maybe)
              ...
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Sure, the code might be lucky enough work "most" of the time.  However, you will suffer a thousand sorrows if both threads try to acquire those locks at about the same time and you have to figure out why your program is mysteriously nonresponsive.  &lt;br /&gt;
&lt;/p&gt;&lt;p&gt;Computer scientists love to spend time thinking about such problems--especially if it means they can make up some diabolical problem about philosophers that they can put on an operating systems exam.  However, I'll spare you the details of that.&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;The problem of deadlock is not something that I would normally spend much time thinking about, but I recently saw some material talking about improved thread support in C++0x.  For example,  &lt;a href="http://www.devx.com/SpecialReports/Article/38883/1954"&gt;this article&lt;/a&gt; has some details.  In particular, it seems that C++0x offers a new locking operation &lt;tt&gt;std::lock()&lt;/tt&gt; that can acquire multiple mutex locks all at once while avoiding deadlock. For example:&lt;br /&gt;
&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;std::unique_lock&amp;lt;std::mutex&gt; lock_a(a.m,std::defer_lock);
std::unique_lock&amp;lt;std::mutex&gt; lock_b(b.m,std::defer_lock);
&lt;b&gt;std::lock(lock_a,lock_b);&lt;/b&gt;      // Lock both locks
...
... do something involving data protected by both locks
...
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;I don't actually know how C++0x implements its &lt;tt&gt;lock()&lt;/tt&gt; operation, but I do know that one way to avoid deadlock is to put some kind of ordering on all of the locks in a program.   If you then strictly enforce a policy that all locks have to be acquired in increasing order, you can avoid deadlock.  Just as an example, if you had two locks A and B, you could assign a unique number to each lock such as A=1 and B=2.  Then, in any part of the program that wanted to acquire both lock A and B, you just make a rule that A always has to be acquired first (because its number is lower).   In such a scheme, the thread &lt;tt&gt;bar()&lt;/tt&gt; shown earlier would simply be illegal.  That &lt;tt&gt;lock()&lt;/tt&gt; operation in C++ is almost certainly doing something similar to this--that is, it knows enough about the locks so that they can acquired without deadlock.&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;All of this got me thinking--I wonder how hard it would be to implement the &lt;tt&gt;lock()&lt;/tt&gt; operation in Python?   Not hard as it turns out.  First step is to change the name--given that &lt;tt&gt;acquire()&lt;/tt&gt; is the typical method used to acquire a lock, let's just call the operation &lt;tt&gt;acquire()&lt;/tt&gt; to make it more clear.  You can define &lt;tt&gt;acquire()&lt;/tt&gt; as a context-manager and simply order locks according to their &lt;tt&gt;id()&lt;/tt&gt; value like this:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;class acquire(object):
    def __init__(self,*locks):
        self.locks = sorted(locks, key=lambda x: id(x))
    def __enter__(self):
        for lock in self.locks:
            lock.acquire()
    def __exit__(self,ty,val,tb):
        for lock in reversed(self.locks):
            lock.release()
        return False
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Okay, that was easy enough to do, but does it work?   Let's try it on the classic dining philosophers problem (look it up if you need a refresher):&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;import threading

# The philosopher thread
def philosopher(left, right):
    while True:
        with acquire(left,right):
             print threading.currentThread(), "eating"

# The chopsticks
NSTICKS = 5
chopsticks = [threading.Lock() 
              for n in xrange(NSTICKS)]

# Create all of the philosophers
phils = [threading.Thread(target=philosopher,
                          args=(chopsticks[n],chopsticks[(n+1) % NSTICKS]))
         for n in xrange(NSTICKS)]

# Run all of the philosophers
for p in phils:
    p.start()
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;If you try this code, you'll find that the philosophers run all day with no deadlock. Just as an experiment, you can try changing the &lt;tt&gt;philosopher()&lt;/tt&gt; implementation to one that acquires the locks separately:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;def philosopher(left, right):
    while True:
        with left:
             with right:
                 print threading.currentThread(), "eating"
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Yep, almost instantaneously deadlock.  So, as you can see, our &lt;tt&gt;acquire()&lt;/tt&gt; operation seems to be working.&lt;/p&gt;&lt;p&gt;There's still one last aspect of this experiment that needs to be addressed.   One potential problem with our &lt;tt&gt;acquire()&lt;/tt&gt; operation is that it doesn't prevent a user from using it in a nested manner as before.  For example, someone might write code like this:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;with acquire(a_lock,b_lock):
     ...
     with acquire(c_lock, d_lock):
          ...
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Catching such cases at the time of definition would be difficult (if not impossible).  However, we could make the &lt;tt&gt;acquire()&lt;/tt&gt; context manager keep a record of all previously acquired locks using a list placed in thread local storage.   Here's a new implementation--and just for kicks, I'm going to switch it over to a context manager defined by a generator (mainly because I can and generators are cool):&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;import threading
from contextlib import contextmanager

local = threading.local()
@contextmanager
def acquire(*locks):
    locks = sorted(locks, key=lambda x: id(x))   
    acquired = getattr(local,"acquired",[])
    # Check to make sure we're not violating the order of locks already acquired   
    if acquired:
        if max(id(lock) for lock in acquired) &gt;= id(locks[0]):
            raise RuntimeError("Lock Order Violation")
    acquired.extend(locks)
    local.acquired = acquired
    try:
        for lock in locks:
            lock.acquire()
        yield
    finally:
        for lock in reversed(locks):
            lock.release()
        del acquired[-len(locks):]
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;If you use this version, you'll find that the philosophers work just fine as before.  However, now consider this slightly modified version with the nested acquires:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;# The philosopher thread                                                                                             
def philosopher(left, right):
    while True:
        with acquire(left):
            with acquire(right):
                print threading.currentThread(), "eating"
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Unlike the previous version that had nested &lt;tt&gt;with&lt;/tt&gt; statements and deadlocked, this one runs.  However, one of the philosophers crashes with a nasty traceback:&lt;/p&gt;&lt;blockquote&gt;&lt;pre&gt;Exception in thread Thread-5:
Traceback (most recent call last):
  File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/threading.py", line 522, in __bootstrap_inner
    self.run()
  File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/threading.py", line 477, in run
    self.__target(*self.__args, **self.__kwargs)
  File "hier4.py", line 53, in philosopher
    with acquire(right):
  File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/contextlib.py", line 16, in __enter__
    return self.gen.next()
  File "hier4.py", line 35, in acquire
    raise RuntimeError("Lock Order Violation")
RuntimeError: Lock Order Violation
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Very good.  That's exactly what we wanted.&lt;/p&gt;&lt;p&gt;So, what's the moral of this story.  First of all, I don't think you should use this as a license to go off and write a bunch of multithreaded code that relies on nested lock acquisitions.  Sure, the context manager might catch some potential problems, but it won't change the fact that you'll still want to blow your head off after debugging some other horrible problem that comes up with your overly clever and/or complicated design.&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;I think the main take-away is an appreciation for Python's context-manager feature.  There's so much more you can do with a context manager than simply closing a file or releasing an individual lock.&lt;/p&gt;&lt;p&gt;Disclaimer: I didn't do a hugely exhaustive internet search to see if anyone else had implemented anything similar to this in Python.  If you know of some links to related work, tell me.  I'll add them here.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-84152637118440889?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2009/11/python-thread-deadlock-avoidance_20.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>3</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-3077934970165352710</guid><pubDate>Sat, 31 Oct 2009 16:19:00 +0000</pubDate><atom:updated>2009-10-31T09:19:25.297-07:00</atom:updated><title>Ultimate Python Quickstart Guide</title><description>&lt;p&gt;As the father of a toddler and a newborn, I've been getting my fair share of practice putting together various sorts of baby accessories (strollers, bassinets, cribs, etc.).  It has inspired me to write this ultimate quick start guide to getting started with the Python programming language.  I hope that you find it to be as incredibly useful as I have.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Congratulations!&lt;/b&gt;&lt;br /&gt;
&lt;p&gt;Congratulations on your wise decision to use Python!  Follow this quick and easy guide to get started.&lt;/p&gt;&lt;br /&gt;
&lt;center&gt;&lt;br /&gt;
(a) Get&lt;br&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/DownloadPython.png"&gt;&lt;/br&gt;&lt;br /&gt;
&lt;p&gt;(b) Click &lt;br&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/InstallPython.JPG"&gt;&lt;/br&gt;&lt;br /&gt;
&lt;p&gt;(c) Run &lt;br&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/RunPython.JPG"&gt;&lt;/br&gt;&lt;br /&gt;
&lt;p&gt;(d) Code &lt;br&gt;&lt;br /&gt;
&lt;img src="http://www.dabeaz.com/images/CodePython.png"&gt;&lt;/br&gt;&lt;br /&gt;
&lt;p&gt;&lt;/center&gt;&lt;br /&gt;
&lt;p&gt;Enjoy your new Python interpreter!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-3077934970165352710?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2009/10/ultimate-python-quickstart-guide.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>2</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-1146030503757846605</guid><pubDate>Mon, 14 Sep 2009 00:43:00 +0000</pubDate><atom:updated>2009-09-13T17:43:39.500-07:00</atom:updated><title>Python Thread Synchronization Primitives : Not Entirely What You Think</title><description>&lt;p&gt;If you have done any kind of programming with Python threads, you are probably familiar with the basic synchronization primitives provided by the &lt;a href="http://docs.python.org/library/threading"&gt;threading&lt;/a&gt; module.  Specifically, you get the following kinds of synchronization objects to work with:&lt;/p&gt;&lt;p&gt;&lt;ul&gt;&lt;li&gt;&lt;tt&gt;Lock&lt;/tt&gt;.   Mutual exclusion lock that's commonly used to protect shared data structures. &lt;/li&gt;
&lt;li&gt;&lt;tt&gt;RLock&lt;/tt&gt;. Reentrant mutual exclusion lock that is useful for code-based locking on functions or methods or to implement monitors. &lt;/li&gt;
&lt;li&gt;&lt;tt&gt;Event&lt;/tt&gt;.  An object that that allows one or more threads to wait for some "event" to occur.  Used to implement barriers or to signal the completion of some task. &lt;/li&gt;
&lt;li&gt;&lt;tt&gt;Condition&lt;/tt&gt;.  Condition variable.  Used to send signals between threads.  For example in producer-consumer problems, the producer will use a condition variable to send a signal to the consumer that data is available.&lt;/li&gt;
&lt;li&gt;&lt;tt&gt;Semaphore&lt;/tt&gt;. A high-level synchronization primitive based on an integer counter;  Acquiring the semaphore decreases the counter and releasing the semaphore increases the counter.  If the counter is 0 and a thread tries to acquire, it will block until a different thread releases the semaphore. &lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;&lt;p&gt;Knowing how and when to use the various synchronization primitives is often a non-trivial exercise.   However, the point of this post is not about that--so if you're here looking for a gentle tutorial, you're in the wrong place.&lt;/p&gt;&lt;p&gt;Instead, I'd like to look at the inner workings of Python's thread synchronization primitives. In part, this is motivated by a general interest in knowing how Python works on multicore machines. However, it's also related to something that I noticed when putting my GIL talk together.  So, we'll take a little tour under the covers, do a few experiments, and think about how this might fit into the "big picture."&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;&lt;b&gt;A Curious Fact&lt;/b&gt;&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;If you write threaded programs, you should know that Python uses real system-level threads to carry out its work.  That is, threads are implemented using pthreads or some other native threading mechanism provided by the operating system.  However, the same can not be said of Python's basic synchronization primitives such as &lt;tt&gt;Lock&lt;/tt&gt;, &lt;tt&gt;Condition&lt;/tt&gt;, &lt;tt&gt;Semaphore&lt;/tt&gt; and so forth.  That is, even though low-level libraries such as pthreads provide various kinds of basic locks and synchronization objects, the &lt;tt&gt;threading&lt;/tt&gt; library doesn't make direct use of them (so, when you're using something like a &lt;tt&gt;Lock&lt;/tt&gt; object in your program, you're not manipulating a pthreads mutex). &lt;/p&gt;&lt;p&gt;This fact may surprise experienced programmers.  Many of Python's core library modules provide a direct interface to low-level functionality written in C (e.g., think about the &lt;tt&gt;os&lt;/tt&gt; or &lt;tt&gt;socket&lt;/tt&gt; modules).  However, thread synchronization objects are an exception to that rule.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Some History&lt;/b&gt;&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;Python has included support for threads for most of its history.  In fact, if Guido ever gets around to updating his &lt;a href="http://python-history.blogspot.com/"&gt;History of Python&lt;/a&gt; blog, he will eventually tell you that threads were first added to Python in 1992 after a contribution by one of his coworkers Sjoerd Mullender (disclaimer: I don't have a time machine, but I have seen the entire "History of Python" article that Guido is using as the basis for his history blog).  This early work is where you find the introduction of the global interpreter lock (GIL) as well as the low-level &lt;a href="http://docs.python.org/library/thread"&gt;&lt;tt&gt;thread&lt;/tt&gt;&lt;/a&gt; library module. &lt;/p&gt;&lt;p&gt;Part of the problem faced by early versions of Python was the fact that thread programming interfaces weren't always available or standardized across systems.  Thus, threads were only supported on certain machines such as SGI Irix and Sun Solaris.  The pthreads interface wasn't standardized until a little later (~1995).  The modern &lt;a href="http://docs.python.org/library/threading"&gt;&lt;tt&gt;threading&lt;/tt&gt;&lt;/a&gt; library that virtually all Python programmers now use first appeared in Python-1.5.1 (1998).&lt;/p&gt;&lt;p&gt;A consequence of this chaos was that Python's support for threads was intentionally designed to have a minimal set of basic requirements.  The &lt;tt&gt;thread&lt;/tt&gt; library module simply provided a function for launching a Python callable in its own execution thread.  A single function, &lt;tt&gt;allocate_lock()&lt;/tt&gt; could be used to allocate a "lock" object.  This object provided the usual &lt;tt&gt;acquire()&lt;/tt&gt; and &lt;tt&gt;release()&lt;/tt&gt; operations, but not much else.&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;If you dig into the C implementation of the interpreter, you'll find that all support for locking is reduced to just four C functions.&lt;/p&gt;&lt;p&gt;&lt;ul&gt;&lt;li&gt;&lt;tt&gt;PyThread_allocate_lock()&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt&gt;PyThread_free_lock()&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt&gt;PyThread_acquire_lock()&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt&gt;PyThread_release_lock()&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;&lt;p&gt;You can find these functions in a series of files such as &lt;tt&gt;thread_pthread.h&lt;/tt&gt;, &lt;tt&gt;thread_nt.h&lt;/tt&gt;, &lt;tt&gt;thread_solaris.h&lt;/tt&gt;, and so forth in the &lt;tt&gt;Python/&lt;/tt&gt; directory of the Python interpreter source.  Each file simply contains a platform specific implementation of a basic lock.  This lock then becomes the basis for all other synchronization primitives as we'll see in a minute.  It should also be noted that these functions are also used to implement the infamous global interpreter lock (GIL).&lt;/p&gt;&lt;p&gt;&lt;b&gt;What is a lock exactly?&lt;/b&gt;&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;If you have worked with thread locking in C, you might think that the above C functions are simply a wrapper around something like a pthreads mutex lock. However, this is not the case.  Instead, the lock is minimally implemented as a &lt;a href="http://en.wikipedia.org/wiki/Semaphore_(programming)"&gt;binary semaphore&lt;/a&gt;.  Here is a simplified example of the lock implementation that's used on many Unix systems:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;#include &amp;lt;stdlib.h&gt;
#include &amp;lt;pthread.h&gt;
#include &amp;lt;string.h&gt;

typedef struct {
  char           locked;
  pthread_cond_t lock_released;
  pthread_mutex_t mut;
} lock_t;

lock_t *
allocate_lock(void) {
  lock_t *lock;
  lock = (lock_t *) malloc(sizeof(lock_t));
  memset((void *)lock, '\0', sizeof(lock_t));
  pthread_mutex_init(&amp;lock-&gt;mut,NULL);
  pthread_cond_init(&amp;lock-&gt;lock_released, NULL);
  return lock;
}

void 
free_lock(lock_t *lock) {
  pthread_mutex_destroy( &amp;lock-&gt;mut );
  pthread_cond_destroy( &amp;lock-&gt;lock_released );
  free((void *)lock);
}

int 
acquire_lock(lock_t *lock, int waitflag) {
  int success;
  pthread_mutex_lock( &amp;lock-&gt;mut );
  success = lock-&gt;locked == 0;

  if ( !success &amp;&amp; waitflag ) {
    while ( lock-&gt;locked ) {
      pthread_cond_wait(&amp;lock-&gt;lock_released,&amp;lock-&gt;mut);
    }
    success = 1;
  }
  if (success) lock-&gt;locked = 1;
  pthread_mutex_unlock( &amp;lock-&gt;mut );
  return success;
}

void 
release_lock(lock_t *lock) {
  pthread_mutex_lock( &amp;lock-&gt;mut );
  lock-&gt;locked = 0;
  pthread_mutex_unlock( &amp;lock-&gt;mut );
  pthread_cond_signal( &amp;lock-&gt;lock_released );
}
&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;Understanding this code requires some careful study.  However, the key part of it is that Python lock objects manually keep track of their internal state (locked or unlocked).  This is the &lt;tt&gt;locked&lt;/tt&gt; attribute of the lock structure.  The pthreads mutex lock is simply being used to synchronize access to the &lt;tt&gt;locked&lt;/tt&gt; attribute in the &lt;tt&gt;acquire()&lt;/tt&gt; and &lt;tt&gt;release()&lt;/tt&gt; operations (note: this mutex lock is not actually &lt;em&gt;the&lt;/em&gt; lock).  Finally, the condition variable is being used to perform a kind of thread signaling that's used to wake up any sleeping threads when the lock gets released.&lt;/p&gt;&lt;p&gt;&lt;b&gt;What about Native Semaphores?&lt;/b&gt;&lt;/p&gt;&lt;P&gt;As just mentioned, the Python lock is minimally implemented as a binary semaphore.  If you've done thread programming in C, you probably know that many systems optionally include a native semaphore object. On such systems, Python may be built in a way so that it simply uses the native semaphore object for the lock.  For example, this what Python uses for synchronization on Windows.&lt;/P&gt;&lt;p&gt;I don't intend to say any more about this here except to emphasize that using some kind of semaphore is actually a requirement for other parts of Python's threading to work correctly.  For instance, the high-level threading library won't work if the lock isn't implemented in this manner.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Semaphores vs. Mutex Locks&lt;/b&gt;&lt;/p&gt;&lt;p&gt;The differences between a semaphore and mutex lock are subtle.  However, the most obvious one pertains to the issue of ownership.   When you use a mutex lock, there is almost always a strong sense of ownership.  Specifically, if a thread acquires a mutex, it is the only thread that is allowed to release it.  Semaphores don't have this restriction.  In fact, once a semaphore has been acquired, any thread can later release it. This allows for more varied forms of thread signaling and synchronization.  Here is one such experiment you can try in Python:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;import threading, time&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;done = threading.Lock()&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;def foo():&lt;/b&gt;
...      &lt;b&gt;print "I'm foo and I'm running"&lt;/b&gt;
...      &lt;b&gt;time.sleep(30)&lt;/b&gt;
...      &lt;b&gt;done.release()&lt;/b&gt;       # Signal completion by releasing the lock
...
&gt;&gt;&gt; &lt;b&gt;done.acquire()&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;threading.Thread(target=foo).start()&lt;/b&gt;
I'm foo and I'm running
&gt;&gt;&gt; &lt;b&gt;done.acquire(); print "Foo done"&lt;/b&gt;
Foo done                        (note: prints after 30 seconds)
&gt;&gt;&gt;
&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;In this example, a lock is being used to signal the completion of some task.  The main thread acquires the lock to clear it and then launches a thread to carry out some work.  Immediately after launching this thread, the main thread attempts to immediately acquire the lock again.  Since the lock was already in use, this operation blocks.  However, when the worker thread finishes, it releases the lock--notifying the main thread that it has finished.  It is critical to emphasize that the lock is being acquired and released by two different threads.  This is the essential property provided by using a semaphore.  If a traditional mutex lock were used, the program would deadlock or crash with an error.&lt;/p&gt;&lt;p&gt;Just as aside, I would not recommend writing Python code that uses &lt;tt&gt;Lock&lt;/tt&gt; objects in this way.  Most programmers are going to associate &lt;tt&gt;Lock&lt;/tt&gt; with a mutex-lock.  You definitely don't use mutex-locks in the manner shown. &lt;/p&gt;&lt;p&gt;Other differences between mutex locks and semaphores tend to be more subtle. There are a number of well-known problems concerning mutex locks that typically get addressed by thread libraries and the operating system.  For example, the system may implement policies to prevent thread starvation or provide some sense of fairness when many threads are competing for the same lock.   If threads have different scheduling priorities, the system may also try to work around problems related to priority inversion (a problem where a low-priority thread holds a lock needed by a high-priority thread).  Semaphores aren't necessarily treated in the same manner which means that a multithreaded program using semaphores may execute in a manner that is slightly different than one that uses mutex locks.  For now, however, let's skip though details.&lt;/p&gt;&lt;p&gt;&lt;b&gt;The threading Library&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Now, that we've talked about the low-level locking mechanism used by the interpreter, let's talk about the synchronization primitives defined in the &lt;tt&gt;threading&lt;/tt&gt; library.  With the exception of &lt;tt&gt;Lock&lt;/tt&gt; objects, which are identical to the lock described in the above section, all of the other synchronization primitives are written entirely in Python.  For example, consider the &lt;tt&gt;RLock&lt;/tt&gt; implementation.  Here is a cleaned up version of how it is implemented:&lt;br /&gt;
&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;class RLock:
    def __init__(self):
        self._block = _allocate_lock()
        self._owner = None
        self._count = 0

    def acquire(self, blocking=1):
        me = current_thread()
        if self._owner is me:
            self._count = self._count + 1
            return 1
        rc = self._block.acquire(blocking)
        if rc:
            self._owner = me
            self._count = 1
        return rc

    def release(self):
        if self._owner is not current_thread():
            raise RuntimeError("cannot release un-aquired lock")
        self._count = count = self._count - 1
        if not count:
            self._owner = None
            self._block.release()
&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;The fact that an &lt;tt&gt;RLock&lt;/tt&gt; is implemented entirely as a Python layer over a regular lock object significantly impacts its performance.   For example:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;from timeit import timeit&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;timeit("lock.acquire();lock.release()","from threading import Lock; lock = Lock()")&lt;/b&gt;
0.50123405456542969
&gt;&gt;&gt; &lt;b&gt;timeit("lock.acquire();lock.release()","from threading import RLock; lock = RLock()")&lt;/b&gt;
5.2153160572052002
&gt;&gt;&gt;
&lt;/pre&gt;&lt;/blockquote&gt;&lt;p&gt;Here, you see that acquiring and releasing a &lt;tt&gt;RLock&lt;/tt&gt; object is about ten times slower than using a &lt;tt&gt;Lock&lt;/tt&gt;.   The performance impact is worse for more advanced synchronization primitives.  For example, here is the result of using a &lt;tt&gt;Semaphore&lt;/tt&gt; object (which is also implemented entirely in Python)&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;timeit("lock.acquire();lock.release()","from threading import Semaphore; lock = Semaphore(1)")&lt;/b&gt;
6.5345189571380615
&gt;&gt;&gt; 
&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;&lt;tt&gt;Condition&lt;/tt&gt; and &lt;tt&gt;Event&lt;/tt&gt; objects are also implemented entirely in Python. However, their implementation is also rather interesting.   Keep in mind that the primary purpose of a &lt;tt&gt;Condition&lt;/tt&gt; object is to perform signaling between threads.  Here is a very common scenario that you see with producer-consumer problems such as in the implementation of a queue.&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;from threading import Lock, Condition
from collections import deque

items      = deque()
items_cv   = Condition()

def producer():
    while True:
         # produce some item
         items_cv.acquire()
         items.append(item)
         items_cv.notify()
         items_cv.release()

def consumer():
    while True:
         items_cv.acquire()
         while not items:
               items_cv.wait()
         item = items.popleft()
         items_cv.release()
         # Do something with item
&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;Of particular interest here are the &lt;tt&gt;wait()&lt;/tt&gt; and &lt;tt&gt;notify()&lt;/tt&gt; operations that perform the thread signaling.  This signaling is actually carried out using a &lt;tt&gt;Lock&lt;/tt&gt; object.  When you wait on a condition variable, a new &lt;tt&gt;Lock&lt;/tt&gt; object is created and acquired.  The lock is then acquired again to force the thread to block.  If you look at the implementation of &lt;tt&gt;Condition&lt;/tt&gt; you find code like this:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;class Condition:
    ...
    def wait(self, timeout=None):
        ...
        waiter = _allocate_lock()
        waiter.acquire()
        self._waiters.append(waiter)
        ...
        waiter.acquire()       # Block
    ...
&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;The &lt;tt&gt;notify()&lt;/tt&gt; operation that wakes up a thread is carried out by simply releasing the waiter lock created above:&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;class Condition:
    ...
    def notify(self, n=1):
        waiters = self._waiters[:n]
        for waiter in waiters:
            waiter.release()
    ...
&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;Needless to say, a lot of processing is going on underneath the covers when you use something like a &lt;tt&gt;Condition&lt;/tt&gt; object in Python.   Every &lt;tt&gt;wait()&lt;/tt&gt; operation involves creating an entirely new lock object.  Signaling is carried out with &lt;tt&gt;acquire()&lt;/tt&gt; and &lt;tt&gt;release()&lt;/tt&gt; operations on that lock.  Moreover, there are additional locking operations carried out on the lock object associated with the condition variable itself.  Plus, consider that all of this high-level locking actually involves more locks and condition variables in C. &lt;/p&gt;&lt;p&gt;&lt;b&gt;Who Cares?&lt;/b&gt;&lt;/p&gt;&lt;p&gt;At this point, you might be asking yourself "who cares? This is all a bunch of low-level esoteric details."   However, I think that anyone who is serious about using threads in Python should take an interest in how the synchronization primitives are actually put together.&lt;/p&gt;&lt;p&gt;For one, a common rule of thumb with thread programming is to try and avoid the use of locks and synchronization primitives as much as possible.  This is certainly true in C, but even more so in Python.  The fact that almost all of the synchronization primitives are implemented in Python means that they are substantially slower than any comparable operations in a C/C++ threading library.  So, if you care about performance, using a lot of locks is something you'll definitely want to avoid.&lt;/p&gt;&lt;p&gt;The other reason to care about this concerns the &lt;tt&gt;Queue&lt;/tt&gt; module.  It is commonly advised that the Queue module be used as a means for exchanging data between threads because it already deals with all of the underlying synchronization. This is all well and good except for the fact that &lt;tt&gt;Queue&lt;/tt&gt; objects add even more layers to all of the synchronization primitives that we've talked about.   In particular, the locking performed by a queue is done using a combination of locks and condition variables from the &lt;tt&gt;threading&lt;/tt&gt; module. &lt;/p&gt;&lt;p&gt;This means that if you're using queues, you're not really avoiding all of the overhead of locking.  Instead, you're just moving it to a different location where it's out of view.&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;One might wonder just how much overhead gets added by all of this.  For instance, a &lt;tt&gt;Queue&lt;/tt&gt; object is really just a wrapper around a &lt;tt&gt;collections.deque&lt;/tt&gt; with the added locking.   You can try a few performance experiments. For instance, inserting items:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;timeit("q.append(1)","from collections import deque; q = deque()")&lt;/b&gt;
0.17505884170532227
&gt;&gt;&gt; &lt;b&gt;timeit("q.put(1)","from Queue import Queue; q = Queue()")&lt;/b&gt;
4.4164938926696777
&gt;&gt;&gt; 
&lt;/pre&gt;&lt;/blockquote&gt;&lt;/P&gt;&lt;p&gt;Here, you find that inserting into a &lt;tt&gt;Queue&lt;/tt&gt; is about 25 times slower than inserting into a &lt;tt&gt;deque&lt;/tt&gt;. You get similar figures for removing items.   Keep in mind that these simple benchmarks don't even cover the case of working with multiple threads where even more overhead would be added.&lt;br /&gt;
&lt;/p&gt;&lt;P&gt;&lt;b&gt;Some Final Thoughts&lt;/b&gt;&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;There surely seems to be an opportunity for some experimentation with better implementations of Python's thread synchronization primitives. For example, condition variables are a core component of Python's &lt;tt&gt;Semaphore&lt;/tt&gt;, &lt;tt&gt;Event&lt;/tt&gt;, and &lt;tt&gt;Queue&lt;/tt&gt; objects, yet Python makes no effort to use any kind of native implementation (e.g., pthreads condition variables).  Moreover, why is Python using custom implementations of synchronization objects already provided by the operating system and thread libraries (e.g., semaphores).  Given that much of Python's thread implementation was worked out more than ten years ago, it would be interesting to perform some experiments and revisit the threading implementation on modern systems--especially in light of the increased interested in concurrency, multiple CPU cores, and other matters.&lt;br /&gt;
&lt;/p&gt;&lt;p&gt;Anyways, that's it for now.   I'd love to hear your comments.  Also, if you are aware of prior work related to optimizing the threading library, benchmarks, or anything else that might be related, I'd be interested in links so that I can post them here. &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-1146030503757846605?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2009/09/python-thread-synchronization.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>1</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-8353460319128031213</guid><pubDate>Thu, 27 Aug 2009 12:39:00 +0000</pubDate><atom:updated>2009-09-06T16:58:48.798-07:00</atom:updated><category domain='http://www.blogger.com/atom/ns#'>threads</category><category domain='http://www.blogger.com/atom/ns#'>python</category><category domain='http://www.blogger.com/atom/ns#'>gil</category><title>Inside the "Inside the Python GIL" Presentation</title><description>&lt;p&gt;On June 11, 2009 I gave a &lt;a href="http://www.dabeaz.com/python/GIL.pdf"&gt;presentation&lt;/a&gt; about the inner workings of the Python GIL at the &lt;a href="http://chipy.org/"&gt;Chicago Python user group&lt;/a&gt; meeting.   To be honest, I always expected the event to be a pretty low-key affair involving some local Python hackers and some beers.   However, the presentation went a little viral and I've received a number of requests to get the code modifications I made to investigate thread behavior--especially the traces that show thread switching and other details.&lt;/p&gt;&lt;p&gt;In this post, I'll briefly outline the code changes I made to generate the traces.  Before going any further, you should probably first view the original &lt;a href="http://blip.tv/file/2232410/"&gt;presentation&lt;/a&gt;. Also, as a disclaimer, none of these changes are easily packaged into a neat "patch" that one can simply download and install into any Python distribution.  So, to start, you should first go download a Python source distribution for the version of Python you want to experiment with.  For my talk, I was using Python 2.6.&lt;/p&gt;&lt;p&gt;First, let's talk about a major issue--any investigation of threads at a low-level (especially thread scheduling) tends to be a rather tricky affair involving some kind of computer science variant of the uncertainty principle.   That is, once you start trying to observe thread behavior, you run the risk of changing the very thing you're trying to observe.  The problem gets worse if you add a lot of extra complexity--especially if there are extra system calls or I/O.   So, a major underlying concern was to try and devise a technique for recording thread behavior in a minimally invasive manner (as an aside, I considered the idea of trying to use dtrace for this, but decided that it would take longer for me to learn dtrace than it would to simply make a few minor modifications to the interpreter).  &lt;/p&gt;&lt;p&gt;&lt;b&gt;Step 1: Defining time&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Everything that happens inside the Python interpreter is focused around the concept of "ticks."  Each tick loosely corresponds to a single instruction in the virtual machine.  Locate the file &lt;tt&gt;Python/ceval.c&lt;/tt&gt; in the Python source code.   In this file, you will find a global variable &lt;tt&gt;_Py_Ticker&lt;/tt&gt; holding the tick counter.  Here's what the code looks like:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre class="prettyprint"&gt;/* ceval.c */
...
int _Py_CheckInterval = 100;
volatile int _Py_Ticker = 0; /* so that we hit a "tick" first thing */
...&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;Add a new variable declaration &lt;tt&gt;_Py_Ticker_Counter&lt;/tt&gt; to this code so that it looks like this:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre class="prettyprint"&gt;/* ceval.c */
...
int _Py_CheckInterval = 100;
volatile int _Py_Ticker = 0; /* so that we hit a "tick" first thing */
&lt;span style="color:red;"&gt;volatile int _Py_Ticker_Count = 0;&lt;/span&gt;
...&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;Later in the same file, you will find code that decrements the value of &lt;tt&gt;_Py_Ticker&lt;/tt&gt;.  Modify this code so that each time &lt;tt&gt;_Py_Ticker&lt;/tt&gt; reaches 0, the value of &lt;tt&gt;_Py_Ticker_Count&lt;/tt&gt; is incremented.  Here's what it looks like:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;/* ceval.c */
...
  if (--_Py_Ticker &amp;lt; 0) {
   if (*next_instr == SETUP_FINALLY) {
    /* Make the last opcode before
       a try: finally: block uninterruptable. */
    goto fast_next_opcode;
   }
   _Py_Ticker = _Py_CheckInterval;
&lt;font color="red"&gt;   _Py_Ticker_Count++; &lt;/font&gt;
   tstate-&gt;tick_counter++;
...&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;The &lt;tt&gt;_Py_Ticker_Count&lt;/tt&gt; and &lt;tt&gt;_Py_Ticker&lt;/tt&gt; variables together define a kind of internal clock.   &lt;tt&gt;_Py_Ticker&lt;/tt&gt; is a countdown to the next time the interpreter might thread-switch.   The &lt;tt&gt;_Py_Ticker_Count&lt;/tt&gt; keeps track of how many times the interpreter has actually signaled the operating system to schedule waiting threads (if any).  In the traces that follow, these two values are used together to record the sequence of events that occur in terms of interpreter ticks.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Step 2 : Recording Trace Data&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Python defines a general purpose lock object that is used for both the GIL and locking primitives in the threading modules.  On Unix systems using pthreads, the implementation of the lock can be found in the file &lt;tt&gt;Python/thread_pthread.h&lt;/tt&gt;.   In that file, there are two functions that we are going to modify:  &lt;tt&gt;PyThread_acquire_lock()&lt;/tt&gt; and &lt;tt&gt;PyThread_release_lock()&lt;/tt&gt;.&lt;/p&gt;&lt;p&gt;Here's the general idea : The lock/unlock functions are instrumented to record a large in-memory trace of lock-related events.  These include lock entry (when a thread first tries to acquire a lock), busy (when the lock is busy), retry (a repeated failed attempt to acquire a lock), acquire (lock successfully acquired), and release (lock released).  In addition to events, the trace records current values of the &lt;tt&gt;_Py_Ticker&lt;/tt&gt; and &lt;tt&gt;_Py_Ticker_Count&lt;/tt&gt; variables as well as the pointer to the currently executing thread.&lt;/p&gt;&lt;p&gt;All trace data is stored entirely in memory as programs execute.   The size of the history can be controlled with a macro in the code.  To dump the trace, a function &lt;tt&gt;print_history()&lt;/tt&gt; is registered to execute on interpreter exit using the &lt;tt&gt;atexit()&lt;/tt&gt; call.    It is important to emphasize that no I/O occurs as programs are executing--traces are only dumped on program exit.&lt;/p&gt;&lt;p&gt;Here a copy of the modified code.  Be aware that &lt;tt&gt;thread_pthread.h&lt;/tt&gt; is a bit of a mess and that there are a few different implementations of locks.  This code is meant to go in the non-semaphore implemention of locks.    Further discussion appears afterwards&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;/* thread_pthread.h */
...
&lt;span style="color:red;"&gt;/* Thread lock monitoring modifications (beazley) */

#include &amp;lt;sys/resource.h&gt;
#include &amp;lt;sched.h&gt;

#define MAXHISTORY 5000000
static int           thread_history[MAXHISTORY];
static unsigned char tick_history[MAXHISTORY];
static int           tick_count_history[MAXHISTORY];
static unsigned char tick_acquire[MAXHISTORY];
static double        time_history[MAXHISTORY];
static unsigned int  history_count = 0;

#define EVENT_ENTRY   0
#define EVENT_BUSY    1
#define EVENT_RETRY   2
#define EVENT_ACQUIRE 3
#define EVENT_RELEASE 4

static char *_codes[] = {"ENTRY","BUSY","RETRY","ACQUIRE","RELEASE" };

static void print_history(void) {
 int i;
 FILE *f;

 f = fopen("tickhistory.txt","w");
 for (i = 0; i &amp;lt; history_count; i++) {
   fprintf(f,"%x %d %d %s %0.6f\n",thread_history[i],tick_history[i],tick_count_history[i],_codes[tick_acquire[i]],time_history[i]);
 }
 fclose(f);
}

/* External variables recorded in the history */
extern volatile int _Py_Ticker;
extern volatile int _Py_Ticker_Count;

&lt;/span&gt;
int
PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
{
 int success;
 pthread_lock *thelock = (pthread_lock *)lock;
 int status, error = 0;
 &lt;span style="color:red;"&gt;int start_thread = 0;

 if (history_count == 0) {
   atexit(print_history);
 }&lt;/span&gt;

 dprintf(("PyThread_acquire_lock(%p, %d) called\n", lock, waitflag));

 status = pthread_mutex_lock( &amp;amp;thelock-&gt;mut );

 &lt;span style="color:red;"&gt;/* Record information in the log */
 start_thread = (int) pthread_self(); 
 if (history_count &amp;lt; MAXHISTORY) {
   thread_history[history_count] = start_thread;
   tick_history[history_count] = _Py_Ticker;
   tick_count_history[history_count] = _Py_Ticker_Count;
   time_history[history_count] = 0.0;
   tick_acquire[history_count++] = EVENT_ENTRY;
 }&lt;/span&gt;

 CHECK_STATUS("pthread_mutex_lock[1]");
 success = thelock-&gt;locked == 0;

 if ( !success &amp;amp;&amp;amp; waitflag ) {

   &lt;span style="color:red;"&gt;int ntries = 0;&lt;/span&gt;
  /* continue trying until we get the lock */

  /* mut must be locked by me -- part of the condition
   * protocol */

  while ( thelock-&gt;locked ) {
    &lt;span style="color:red;"&gt;if (ntries == 0) {
      if (history_count &amp;lt; MAXHISTORY) {
        thread_history[history_count] = start_thread;
        tick_history[history_count] = _Py_Ticker;
        tick_count_history[history_count] = _Py_Ticker_Count;
        time_history[history_count] = 0.0;
        tick_acquire[history_count++] = EVENT_BUSY;
      }&lt;/span&gt;
    }

   status = pthread_cond_wait(&amp;amp;thelock-&gt;lock_released,
         &amp;amp;thelock-&gt;mut);
   CHECK_STATUS("pthread_cond_wait");
   if (thelock-&gt;locked) {
     &lt;span style="color:red;"&gt;if (history_count &amp;lt; MAXHISTORY) {
       thread_history[history_count] = start_thread;
       tick_history[history_count] = _Py_Ticker;
       tick_count_history[history_count] = _Py_Ticker_Count;
       time_history[history_count] = 0.0;
       tick_acquire[history_count++] = EVENT_RETRY;
       ntries += 1;
     }&lt;/span&gt;
   } else {
     &lt;span style="color:red;"&gt;if (history_count &amp;lt; MAXHISTORY) {
       thread_history[history_count] = start_thread;
       tick_history[history_count] = _Py_Ticker;
       tick_count_history[history_count] = _Py_Ticker_Count;
       {
         struct timeval t;
#ifdef GETTIMEOFDAY_NO_TZ
         if (gettimeofday(&amp;amp;t) == 0)
    time_history[history_count] = (double)t.tv_sec + t.tv_usec*0.000001;
#else /* !GETTIMEOFDAY_NO_TZ */
         if (gettimeofday(&amp;amp;t, (struct timezone *)NULL) == 0)
    time_history[history_count] = (double)t.tv_sec + t.tv_usec*0.000001;
#endif /* !GETTIMEOFDAY_NO_TZ */
       }
       tick_acquire[history_count++] = EVENT_ACQUIRE;
     }&lt;/span&gt;
   }

  }
  success = 1;
 } else {&lt;span style="color:red;"&gt;
   if (history_count &amp;lt; MAXHISTORY) {
     thread_history[history_count] = start_thread;
     tick_history[history_count] = _Py_Ticker;
     tick_count_history[history_count] = _Py_Ticker_Count;
     time_history[history_count] = 0.0;
     tick_acquire[history_count++] = EVENT_ACQUIRE;
   }&lt;/span&gt;
 }
 if (success) thelock-&gt;locked = 1;
 status = pthread_mutex_unlock( &amp;amp;thelock-&gt;mut );
 CHECK_STATUS("pthread_mutex_unlock[1]");

 if (error) success = 0;
 dprintf(("PyThread_acquire_lock(%p, %d) -&gt; %d\n", lock, waitflag, success));
 return success;
}

void
PyThread_release_lock(PyThread_type_lock lock)
{
 pthread_lock *thelock = (pthread_lock *)lock;
 int status, error = 0;

 dprintf(("PyThread_release_lock(%p) called\n", lock));

 status = pthread_mutex_lock( &amp;amp;thelock-&gt;mut );
 CHECK_STATUS("pthread_mutex_lock[3]");
 &lt;span style="color:red;"&gt;
 if (history_count &amp;lt; MAXHISTORY) {
   thread_history[history_count] = (int) pthread_self();
   tick_history[history_count] = _Py_Ticker;
   tick_count_history[history_count] = _Py_Ticker_Count;
   tick_acquire[history_count++] = EVENT_RELEASE;
 }&lt;/span&gt;

 thelock-&gt;locked = 0;

 status = pthread_mutex_unlock( &amp;amp;thelock-&gt;mut );
 CHECK_STATUS("pthread_mutex_unlock[3]");

 /* wake up someone (anyone, if any) waiting on the lock */
 status = pthread_cond_signal( &amp;amp;thelock-&gt;lock_released );
 CHECK_STATUS("pthread_cond_signal");
}&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Step 3 : Rebuilding and Running Python&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Once you have made the above changes, rebuild the Python interpreter and run it on some sample code.  The code should run the same as before, but on program exit, you will get get a huge data file &lt;tt&gt;tickhistory.txt&lt;/tt&gt; dumped into the current working directory.    The contents of this file are going to look something like this:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;a0811720 8 1299 RELEASE 0.000000
a0811720 15 1302 ENTRY 0.000000
a0811720 15 1302 ACQUIRE 0.000000
a0811720 10 1302 ENTRY 0.000000
a0811720 10 1302 ACQUIRE 0.000000
a0811720 10 1302 RELEASE 0.000000
a0811720 7 1302 ENTRY 0.000000
a0811720 7 1302 ACQUIRE 0.000000
b0081000 7 1302 ENTRY 0.000000
b0081000 7 1302 ACQUIRE 0.000000
b0081000 7 1302 RELEASE 0.000000
b0081000 7 1302 ENTRY 0.000000
b0081000 7 1302 ACQUIRE 0.000000
b0081000 7 1302 RELEASE 0.000000
b0081000 7 1302 ENTRY 0.000000
b0081000 7 1302 BUSY 0.000000
a0811720 1 1302 RELEASE 0.000000
a0811720 1 1302 ENTRY 0.000000
a0811720 1 1302 ACQUIRE 0.000000
a0811720 1 1302 ENTRY 0.000000
a0811720 1 1302 ACQUIRE 0.000000
a0811720 100 1303 RELEASE 0.000000
a0811720 100 1303 ENTRY 0.000000
a0811720 100 1303 ACQUIRE 0.000000
a0811720 92 1303 RELEASE 0.000000
a0811720 92 1303 ENTRY 0.000000
a0811720 92 1303 ACQUIRE 0.000000
a0811720 92 1303 ENTRY 0.000000
a0811720 92 1303 ACQUIRE 0.000000
...&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;Be forewarned--the size of this file can be substantial.  Running a threaded program for even 10-20 seconds might generate a trace file that contains 3-4 million events.  To do any kind of analysis on it, you'll probably want to do what everyone normally does and write a Python script. &lt;/p&gt;&lt;p&gt;&lt;b&gt;Discussion&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Interpreting the contents of the trace file are left as an exercise for the reader. However, here are few tips.  First, the normal sequence of lock acquisition and release on the GIL with a CPU-bound thread looks something like this (notice that the &lt;tt&gt;_Py_Ticker&lt;/tt&gt; value in the 2nd column is always 100 and that the lock goes through a repeated ENTRY-&gt;ACQUIRE-&gt;RELEASE cycle): &lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;a000d000 100 3570 ENTRY 0.000000
a000d000 100 3570 ACQUIRE 0.000000
a000d000 100 3571 RELEASE 0.000000
a000d000 100 3571 ENTRY 0.000000
a000d000 100 3571 ACQUIRE 0.000000
a000d000 100 3572 RELEASE 0.000000
a000d000 100 3572 ENTRY 0.000000
a000d000 100 3572 ACQUIRE 0.000000
a000d000 100 3573 RELEASE 0.000000
...&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;If you're looking at thread contention, you're going to see a trace that has an event series of ENTRY-&gt;BUSY-&gt;RETRY-&gt;...-&gt;RETRY-&gt;ACQUIRE-&gt;RELEASE like this:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;span style="color:red;"&gt;a000d000 48 4794 ENTRY 0.000000&lt;/span&gt;
&lt;span style="color:red;"&gt;a000d000 48 4794 BUSY 0.000000&lt;/span&gt;
7091800 32 4794 RELEASE 0.000000
7069a00 32 4794 ACQUIRE 1251397338.473370
7091800 32 4794 ENTRY 0.000000
7091800 32 4794 BUSY 0.000000
&lt;span style="color:red;"&gt;a000d000 32 4794 RETRY 0.000000&lt;/span&gt;
7069a00 100 4795 RELEASE 0.000000
7069a00 100 4795 ENTRY 0.000000
7069a00 100 4795 ACQUIRE 0.000000
&lt;span style="color:red;"&gt;a000d000 66 4795 RETRY 0.000000&lt;/span&gt;
7069a00 100 4796 RELEASE 0.000000
7069a00 100 4796 ENTRY 0.000000
7069a00 100 4796 ACQUIRE 0.000000
&lt;span style="color:red;"&gt;a000d000 95 4796 RETRY 0.000000&lt;/span&gt;
7069a00 100 4797 RELEASE 0.000000
7069a00 100 4797 ENTRY 0.000000
7069a00 100 4797 ACQUIRE 0.000000
...
&lt;span style="color:red;"&gt;a000d000 100 5083 ACQUIRE 1251397338.478188&lt;/span&gt;
...&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt; Here are some other notes concerning its analysis: &lt;ul&gt;&lt;li&gt;The first column is the hex memory address of a lock object.  If you run the program on a threaded program that is using many different locks, you will be tracing not only the GIL, but every lock in the program.   You might be able to use this to investigate lock contention.&lt;/li&gt;
&lt;li&gt;The GIL is not specifically identified in the trace file.  However, it will be one of the first locks used.   &lt;/li&gt;
&lt;li&gt;The last column of the trace file is a system timer that is only recorded when locks are acquired after repeated failed acquisition attempts.   At some point, I was using this to investigate some issues related to response times, but to be honest, I didn't spend much time exploring that angle.   It might be useful if you want to get an idea for how long each thread runs before giving up control.   Of course, you may just want to comment that code out.&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Other Comments&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Since giving the presentation, I've received a few comments through email offering suggestions for a GIL fix.   I stand by my earlier assertion that there is no easy fix for the problem described in the presentation.   Here are some specific suggestions followed by my response: &lt;ul&gt;&lt;li&gt;"Perhaps the GIL could be fixed by adding some kind of scheduling queue."   If you were to add a scheduling queue to the GIL, you would effectively turn it into a kind of poorly implemented mutex lock.    Mutex locks are already implemented (by pthreads and the OS) using queues into order to avoid thread starvation.   More details can be found in an operating system textbook.  You might also look at the &lt;a href="http://en.wikipedia.org/wiki/Lamport%27s_bakery_algorithm"&gt;Bakery Algorithm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;"Perhaps the GIL could be fixed by simply using a mutex lock."   As just mentioned, mutex locks are generally implemented using a queuing mechanism.  If you do this, runnable threads will always context switch every 100 interpreter ticks (you'll see the threads cycling in a round-robin manner).  This will definitely eliminate the multicore contention problem, but now your programs will perform a tremendous amount of context switching.  Also, you might lose the high scheduling priority of I/O bound threads.    Needless to say, there are some downsides that need to be considered (just for the record, I think the use of a condition variable in the current implementation is probably the best overall solution for running on a single CPU). &lt;/li&gt;
&lt;li&gt;"Could you fix the problem by telling the operating system to schedule all threads on the same core?"  Short answer: No.  C extensions to Python (and even significant parts of Python itself) often release the GIL by design so that they can run concurrently while carrying out work that doesn't directly involve the Python interpreter.   If you force everything to one core, you will most likely make these programs run worse, not better.&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Final Words&lt;/b&gt;&lt;/p&gt;&lt;p&gt;As mentioned in the presentation, deep exploration of the Python GIL is not a project I'm actively working on.  In fact, all of this was really just an exploration to find out how the GIL works and to see if I could track down pathological performance for a certain test case on my Mac.    Feel free to take this code and hack it in any way that you wish.  If it proves to be useful, just give me an acknowledgment when you give your PyCon presentation.   Have fun!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-8353460319128031213?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2009/08/inside-inside-python-gil-presentation.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>10</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-7604178517001523239</guid><pubDate>Sun, 09 Aug 2009 22:32:00 +0000</pubDate><atom:updated>2009-09-06T17:03:35.031-07:00</atom:updated><title>Python Binary I/O Handling</title><description>&lt;p&gt;As a followup to my last post about the Essential Reference, I thought I'd talk about the one topic that I wish I had addressed in more detail in my book--and that's the subject of binary data and I/O handling.   Let me elaborate. &lt;/p&gt;&lt;p&gt;One of the things that interests me a lot right now is the subject of concurrent programming.  In the early 1990's, I spent a lot of time writing big physics simulation codes for Connection Machines and Crays.   All of those programs had massive parallelism (e.g., 1000s of processors) and were based largely on message-passing.    In fact, my first use of Python was to control a large massively parallel C program that used MPI.  Now, we're starting to see message passing concepts incorporated into the Python standard library.   For example, I think the inclusion of the multiprocessing library is probably one of the most significant additions to the Python core that has occurred in the past 10 years. &lt;/p&gt;&lt;p&gt;A major aspect of message passing concerns the problem of quickly getting data from point A to point B.   Obviously, you want to do it as fast as possible.  A high speed connection helps.  However, it also helps to eliminate as much processing overhead as possible.  Such overhead can come from many places--decoding data, copying memory buffers, and so forth. &lt;/p&gt;&lt;p&gt;Python makes it pretty easy to pass data around between processes.  For example, you can use the pickle module, json, XML-RPC, or some other similar mechanism.  However, all of these approaches involve a significant amount of overhead to encode and decode data.   You probably wouldn't want to use them for any kind of bulk data transfer (e.g., if you wanted to send a large array of floats between processes).  Nor would you really want to use this for some kind of high-performance networking on a big cluster.&lt;/p&gt;&lt;p&gt;However, lurking within the Python standard library is another way to deal with data in messaging and interprocess communication.   However, it's all spread out in a way that's not entirely obvious unless you're looking for it (and even then it's still pretty subtle).    Let's start with the &lt;a href="http://docs.python.org/library/ctypes.html"&gt;ctypes&lt;/a&gt; library.   I always assumed that ctypes was all about accessing C libraries from Python (an alternative approach to &lt;a href="http://www.swig.org/"&gt;Swig&lt;/a&gt;).  However, that's only part of the story.  For instance, using ctypes, you can define binary data structures:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;from ctypes import *
class Point(Structure):
     _fields_ = [ ('x',c_double), ('y',c_double), ('z',c_double) ]&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;This defines an object representing a C data structure.  You can even create and manipulate such objects just like an ordinary Python class:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;p = Point(2,3.5,6)&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;p.x&lt;/b&gt;
2.0
&gt;&gt;&gt; &lt;b&gt;p.y&lt;/b&gt;
3.5
&gt;&gt;&gt; &lt;b&gt;p.z = 7&lt;/b&gt;
&gt;&gt;&gt;
&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;However, keep in mind that under the covers, this is manipulating a C structure represented in a contiguous block of memory. &lt;/p&gt;&lt;p&gt;Now this is where things start to get interesting.   I wonder how many Python programmers know that they can directly write a ctypes data structure onto a file opened in binary mode.   For example, you can take the point above and do this:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;f = open("foo","wb")&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;f.write(p)&lt;/b&gt;       
&gt;&gt;&gt; &lt;b&gt;f.close()&lt;/b&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;Not only that, you can read the file directly back into a ctypes structure if you use the poorly documented readinto() method of files.&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;g = open("foo","rb")&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;q = Point()&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;g.readinto(q)&lt;/b&gt;
24
&gt;&gt;&gt; &lt;b&gt;q.x&lt;/b&gt;
2.0
&gt;&gt;&gt; &lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;The mechanism that makes all of this work is Python's so-called "buffer protocol."   Since C types structures are contiguous in memory, I/O operations can be performed directly with that memory without making copies or first converting such structures into strings as you might do with something like the struct module.   The buffer protocol simply exposes the underlying memory buffers for use in I/O.&lt;/p&gt;&lt;p&gt;Direct binary I/O like this is not limited to files.  If &lt;tt&gt;s&lt;/tt&gt; is a socket, you can perform similar operations like this:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;p = Point(2,3,4)           #  Create a point
s.send(p)                  #  Send across a socket

q = Point()
s.recv_info(q)               # Receive directly into q
&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;If that wasn't enough to make your brain explode, similar functionality is provided by the multiprocessing library as well.  For example, Connection objects (as created by the multiprocessing.Pipe() function) have send_bytes() and recv_bytes_into() methods that also work directly with ctypes objects.   Here's an experiment to try.  Start two different Python interpreters and define the &lt;tt&gt;Point&lt;/tt&gt; structure above.    Now, try sending a point through a multiprocessing connection object: &lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;p = Point(2,3,4)&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;from multiprocessing.connection import Listener&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;serv = Listener(("",25000),authkey="12345")&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;c = serv.accept()&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;c.send_bytes(p)&lt;/b&gt;
&gt;&gt;&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;In the other Python process, do this:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;&lt;pre&gt;&gt;&gt;&gt; &lt;b&gt;q = Point()&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;from multiprocessing.connection import Client&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;c = Client(("",25000),authkey="12345")&lt;/b&gt;
&gt;&gt;&gt; &lt;b&gt;c.recv_bytes_into(q)&lt;/b&gt;
24
&gt;&gt;&gt; &lt;b&gt;q.x&lt;/b&gt;
2.0
&gt;&gt;&gt; &lt;b&gt;q.y&lt;/b&gt;
3.0
&gt;&gt;&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;As you can see, the point defined in one process has been directly transferred to the other. &lt;/p&gt;&lt;p&gt;If you put all of the pieces of this together, you find that there is this whole binary handling layer lurking under the covers of Python.   If you combine it with something like ctypes, you'll find that you can directly pass binary data structures such as C structures and arrays around between different interpreters.  Moreover, if you combine this with C extensions, it seems to be possible pass data around without a lot of extra overhead.   Finally, if that wasn't enough, it turns out that some popular extensions such as numpy also play in this arena.  For instance, in certain cases you can perform similar direct I/O operations with numpy arrays (e.g., directly passing arrays through multiprocessing connections).&lt;/p&gt;&lt;p&gt;I think that this functionality is pretty interesting--and highly relevant to anyone who is thinking about parallel processing and messaging.   However, all of this is also somewhat unsettling.  For one, much of this functionality is all very poorly documented in the Python documentation (and in my book for that matter).     If you look at the documentation for methods such as the read_into() method files, it simply says "undocumented, don't use it."  The buffer interface, which makes much of this work, has always been rather obscure and poorly understood--although it got a redesign in Python 3.0 (see Travis Oliphant's &lt;a href="http://www.youtube.com/watch?v=10smLBD0kXg"&gt;talk&lt;/a&gt; from PyCon).    And if it wasn't complicated enough already, much of this functionality gets tied into the bytes/Unicode handling part of Python --a hairy subject on its own.  &lt;/p&gt;&lt;p&gt;To wrap up, I think much of what I've described here represents a part of Python that probably deserves more investigation (and at the very least, more documentation).  Unfortunately, I only started playing around with this recently--too late for inclusion in the Essential Reference (which was already typeset and out the door).    However, I'm thinking it might be a good topic for a  PyCon tutorial.   Stay tuned. &lt;/p&gt;&lt;p&gt;Note: If anyone has links to articles or presentations about this, let me know and I'll add them here.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-7604178517001523239?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2009/08/python-binary-io-handling.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>14</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-4272389616836173946</guid><pubDate>Sun, 09 Aug 2009 15:26:00 +0000</pubDate><atom:updated>2009-09-06T17:02:07.789-07:00</atom:updated><category domain='http://www.blogger.com/atom/ns#'>essential reference</category><category domain='http://www.blogger.com/atom/ns#'>python</category><title>Essential Misconceptions</title><description>&lt;p&gt;A few days ago, Mike Riley posted a great &lt;a href="http://dobbscodetalk.com/index.php?option=com_myblog&amp;amp;task=view&amp;amp;id=1696&amp;amp;Itemid=96"&gt;review&lt;/a&gt; of the new "Python Essential Reference, 4th Edition" on Dr. Dobb's CodeTalk.   In that review, he writes:&lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;"While the author could have taken the easy path of regurgitating the online documentation, he has instead reworked the explanation for each class and function call in the Python core library with commendable clarity, frequently accompanying these detailed examinations with extremely useful and meaningful code examples.  The book is also very well designed and organized, making it a snap to find information within a matter of seconds."&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;This is a reviewer who really gets what this book is about.  However, for every great review like this, I also encounter comments that simply dismiss the book out-of-hand saying it "offers nothing" over Python's online documentation.  With all due respect to Python's fine documentation, I beg to differ.   &lt;/p&gt;&lt;p&gt;First and foremost, I've always viewed the Python Essential Reference as a serious programming reference for myself (yes, I always have a copy next to my desk and I use it regularly).  Although, I will admit that Python certainly has a lot of online documentation, it's also missing a lot of essential details. For example, I can't count the number of times I've looked at the online documentation for something only to have to go out and do some kind of extended Google search to fill in a missing detail (or worse, having to load the source code for some module and look through it).&lt;/p&gt;&lt;p&gt;Let's look at an example.   Suppose you're writing some networking code with the socket module and you want to use the recv(bufsize [, flags]) method of a socket.  If you head off to the &lt;a href="http://docs.python.org/library/socket.html"&gt;online documentation&lt;/a&gt; you will certainly find some information. &lt;/p&gt;&lt;p&gt;&lt;blockquote&gt;"Receive data from the socket. The return value is a string representing the data received. The maximum amount of data to be received at once is specified by bufsize. See the Unix manual page recv(2) for the meaning of the optional argument flags; it defaults to zero."&lt;/blockquote&gt;&lt;/p&gt;&lt;p&gt;Yes, this is all very useful.   Especially that part about having to refer to a Unix man page.  I'm sure the Windows programmers find that especially useful.    If you turn to the Essential Reference p. 483, you'll not only find a description, but you will also get a complete table showing you exactly what can be given for flags along with a brief description of each option.   This approach is found throughout the book--with few exceptions are readers simply referred to other documentation.  As another example, I would challenge anyone to effectively use something like the setsockopt() or getsockopt() methods of a socket using nothing by Python's online docs.&lt;/p&gt;&lt;p&gt;The other thing that I've tried to do in the book is answer all sorts of questions about tricky interactions between different parts of Python.  Take, for example, this question:  Can a separate execution thread safely close a generator/coroutine function by invoking the generator's close() method?   Sure, that's not the kind of question that comes up every day, but if you know a thing or two about generators and coroutines, you'll know that they are often used in the context of concurrent programming, just like threads.  Not only that, threads and generators might be used together (for example, using threads to carry out blocking operations).  Thus, it is reasonable to assume that programmers working with both threads and generators in the same program might start to wonder about their possible interaction.  I know I did.&lt;/p&gt;&lt;p&gt;If you try to find an answer to this question using the online documentation, you will be searching for some time and probably come up with nothing.  Although there is plenty of discussion about generators, the yield statement, and other matters, you really don't find much about generators and threads mixed together.  Even PEP 342, the official specification that introduced the generator close() method says nothing on this matter.&lt;/p&gt;&lt;p&gt;Now, let's look at the Essential Reference.  First, if you turn to the index and look up "Threads", you will find about a half-page of subentries.  In fact, there is even an entry labeled "Threads: close() method of generators, p. 104."    If you turn to p. 104, you will find a sentence "if a program is currently iterating on a generator, you should not call close() asynchronously on that generator from a separate thread of execution or from a signal handler."   &lt;/p&gt;&lt;p&gt;This is certainly not the only example, but there are a wide variety of similar questions that I try to address.  For example, can you use a decorator with a recursive function? (p. 113).  Or what is the interaction between the __slots__ feature of a class and inheritance? (p. 133).  Or, does the name mangling of private attributes (e.g., __foo) in a class introduce a runtime performance penalty? (p. 128).  All of these questions fall into a general category of issues related to the "side-effects" of using various Python features.   Although you can find some of this in the online docs, it is often scattered and incomplete.  I've tried to fix that.&lt;/p&gt;&lt;p&gt;Finally, I've really tried to make the Essential Reference a kind of programming "cookbook" of sorts.   Although its primary goal is to be a reference, I have also incorporated a wide variety of practical examples from the Python training courses that I run.   For instance,  if you know about the &lt;a href="http://www.dabeaz.com/generators"&gt;Generators&lt;/a&gt; or &lt;a href="http://www.dabeaz.com/coroutines"&gt;Coroutines&lt;/a&gt; tutorials I presented at PyCON, you'll find similar information.  I also include examples that explore tricky interactions and customization features of certain library modules.    For example, how do I customize an XML-RPC server to only accept connections from known IP addresses? (p. 494).   Or how do I use the ssl module to implement a secure server?  (p. 489).    Many of these examples are related to things that I've had to figure out once before, but can never quite remember on a day-to-day basis.  By putting them in the book, it helps me remember how to do a variety of tricky things.&lt;/p&gt;&lt;p&gt;So, that's about it.   I hope people find the book to be useful.  If so, tell your friends.  If not, feel free to use it for propping up some uneven furniture.    Just don't say that it's the same as the online docs.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-4272389616836173946?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2009/08/essential-misconceptions.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>8</thr:total></item><item><guid isPermaLink='false'>tag:blogger.com,1999:blog-36456651.post-4937612723006367015</guid><pubDate>Sun, 09 Aug 2009 15:01:00 +0000</pubDate><atom:updated>2009-08-09T08:21:59.908-07:00</atom:updated><category domain='http://www.blogger.com/atom/ns#'>introduction</category><title>First post</title><description>Well, this is my blog.  Welcome!   I have to admit that I've never been much of a blogger in the past--preferring to focus my energy on writing books and giving conference presentations.  However, I'll probably use this space to post occasional technical articles about projects that I'm working on as well as followups to my presentations.  Enjoy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/36456651-4937612723006367015?l=www.dabeaz.com%2Fblog%2Fdablog.html' alt='' /&gt;&lt;/div&gt;</description><link>http://www.dabeaz.com/blog/2009/08/first-post.html</link><author>noreply@blogger.com (Dave)</author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>1</thr:total></item></channel></rss>