Tuesday, February 10, 2015

Autocommit with ceODBC is slow

You already know that in Python it is faster to call executemany() than repeatedly calling execute() to INSERT the same number of rows because executemany() avoids rebinding the parameters, but what about the effect of autocommit on performance? While this is probably not specific to ceODBC, using autocommit is astonishingly slow. Here is how slow.

First, the Python code to run the benchmark:

import ceODBC
import datetime
import os
import time

connection_string="driver=sql server;database=database;server=server;" 
print connection_string

conn = None
cursor = None
def init_db():
    import ceODBC
    global conn
    global cursor
    conn = ceODBC.connect(connection_string)
    cursor = conn.cursor()

def table_exists():
    cursor.execute("select count(1) from information_schema.tables where table_name='zzz_ceodbc_test'")
    return cursor.fetchone()[0] == 1

def create_table():
    print('create_table')
    create_sql="""
CREATE TABLE zzz_ceodbc_test (
    col1 INT,
    col2 VARCHAR(50)
) """
    try:
        cursor.execute(create_sql)
        assert(table_exists())
    except:
        import traceback
        traceback.print_exc()

rows = []
for i in xrange(0,10000):
    rows.append((i,'abcd'))

def log_speed(start_time, end_time, records):
    elapsed_seconds = end_time - start_time
    if elapsed_seconds > 0:
        records_second = int(records / elapsed_seconds)
        # make elapsed_seconds an integer to shorten the string format
        elapsed_str = str(
            datetime.timedelta(seconds=int(elapsed_seconds)))
        print("{:,} records; {} records/sec; {} elapsed".format(records, records_second, elapsed_str))
    else:
        print("counter: %i records " % records)

 
 
def benchmark(bulk, autocommit):
    init_db()
    global conn
    global cursor
    conn.autocommit=True
    cursor.execute('truncate table zzz_ceodbc_test')
    
    conn.autocommit = autocommit
    insert_sql = 'insert into zzz_ceodbc_test (col1, col2) values (?,?)'
    
    start_time = time.time()
    if bulk:
        cursor.executemany(insert_sql, rows)
    else:
        for row in rows:
            cursor.execute(insert_sql, row)
    conn.commit()
    end_time = time.time()
    
    cursor.execute("select count(1) from zzz_ceodbc_test")
    assert cursor.fetchone()[0] == len(rows)
    
    log_speed(start_time, end_time, len(rows))
    conn.autocommit=True
    
    del cursor
    del conn
    return end_time - start_time


def benchmark_repeat(bulk, autocommit, repeats=5):
    description = "%s, autocommit=%s" % ('bulk' if bulk else 'one at a time', autocommit)
    print '\n******* %s' % description
    results = []
    for x in xrange(0, repeats):
        results.append(benchmark(bulk, autocommit))
    print results

benchmark_repeat(True, False)
benchmark_repeat(True, True)
benchmark_repeat(False, True)

And to graph the results in R:

results_table <- 'group seconds
bulk_manual 0.6710000038146973
bulk_manual 0.6710000038146973
bulk_manual 0.9830000400543213
bulk_manual 0.7330000400543213
bulk_manual 0.6710000038146973
bulk_auto 8.486999988555908
bulk_auto 8.269000053405762
bulk_auto 8.980999946594238
bulk_auto 8.453999996185303
bulk_auto 8.480999946594238
one_at_a_time 24.391000032424927
one_at_a_time 23.70300006866455
one_at_a_time 71.66299986839294
one_at_a_time 23.58899998664856
one_at_a_time 37.18400001525879'

results <- read.table(textConnection(results_table), header = TRUE)
closeAllConnections() 

library(ggplot2)
ggplot(results, aes(group, seconds)) + geom_boxplot()

Conclusion: executemany() with autocommit is 76% faster than execute(), and executemany() without autocommit is 91% faster than executemany() with autocommit. Also, executemany() gives more consistent performance.

Ran on Windows 7 Pro 64-bit, Python 2.7.9 32-bit, ceODBC 2.0.1, Microsoft SQL Server 11.0 SP1, R 3.1.2.

Wednesday, February 4, 2015

Party like it's 19999 (SAS)

On 03OCT2014 I must have missed the party in Cary, NC.

data _null_;
 format date date9.;
 date = 19999;
 put date=;
run;

So the next party is 03JUN2568?

data _null_;
 format date date9.;
 date = 222222;
 put date=;
run;

Thursday, January 29, 2015

LimeSurvey is allergic to Cloudflare Rocket Loader

In case you use LimeSurvey with Cloudflare, you may want to disable Rocket Loader, which "automatically asynchronously load all JavaScript resources." In LimeSurvey it causes problem saving questions (the save button does not do anything), disables tooltips for buttons in the administrative interface (so the icons are hard to interpret), and maybe causes other problems.

If you are not sure whether Rocket Loader is enabled, just look at the HTML source. If it is enabled, you will see "rocketloader" in the HTML source.

Cloudflare's Auto Minify seems safe to use.

I tested with LimeSurvey Version 2.05+ Build 141229, Firefox 35, and Google Chrome 40.

Wednesday, December 17, 2014

SAS crash with BULKLOAD and ODBC Driver 11 for SQL Server

SAS 9.4 (TS1M2) crashes hard when using BULKLOAD=YES with the latest Microsoft ODBC Driver 11 for SQL Server. For a brief moment you may see in the SAS log ERROR: BCP initialize error: [Microsoft][ODBC Driver 11 for SQL Server]Connection is not enabled for BCP, and then the SAS window closes.

The Windows Event Viewer has the following information:

Faulting application name: sas.exe, version: 9402.0.21456.21239, time stamp: 0x53d05339
Faulting module name: ntdll.dll, version: 6.1.7601.18229, time stamp: 0x51fb1072
Exception code: 0xc0000374
Fault offset: 0x000ce753
Faulting process id: 0x5724
Faulting application start time: 0x01d004d90519e3fc
Faulting application path: C:\Program Files\SASHome2\x86\SASFoundation\9.4\sas.exe
Faulting module path: C:\Windows\SysWOW64\ntdll.dll
Report Id: 4c84f206-70cc-11e4-bd42-0205857feb80

SAS Support acknowledged the crash and will address it in the next maintenance release. At this time I do not see any KB article.

Friday, December 5, 2014

Fibonacci sequence in R and SAS

Because the Fibonacci sequence is simply defined by recursion, it makes for an elegant programming exercise. Here is one way to do it in SAS, and another way to do it in R. I've also included unit testing code to check that it works.

Wednesday, September 17, 2014

Write directory listing to CSV in Windows PowerShell

This one-line command will invoke Windows PowerShell to write a directory listing to a CSV file, which is easy to use in spreadsheets and database programs. It recurses subfolders, and it includes the following information: full file name, creation time, last modified time, file size, and owner (last modified by).

To use it, simply modify the two paths: directory to scan and path to the CSV.

powershell "Get-ChildItem -Recurse c:\directory\to\scan\ | ForEach-Object {$_ | add-member -name "Owner" -membertype noteproperty -value (get-acl $_.fullname).owner -passthru} | Sort-Object fullname | Select FullName,CreationTime,LastWriteTime,Length,Owner | Export-Csv -Force -NoTypeInformation c:\folder\to\directory.csv"

Tested on Windows 7 Enterprise.

I use it as part of an automatic process to archive a folder with Git.

Monday, June 23, 2014

Change the process priority in SAS

This SAS code programmatically changes the process priority for the current SAS program. You may want to use this for SAS batch jobs.

For example, when I run multiprocessor jobs using Ian J. Ghent %multiThreadDataStep macro, the Windows user interface becomes less responsive and my batch job competes with other processes, but using this method addresses both issues.

Simply run the line corresponding to the desired priority level. To decrease process priority, choose one of the last two.

x "wmic.exe process where processid=""&SYSJOBID"" call setpriority 'high priority'";
x "wmic.exe process where processid=""&SYSJOBID"" call setpriority 'above normal'";
x "wmic.exe process where processid=""&SYSJOBID"" call setpriority 'normal'";
x "wmic.exe process where processid=""&SYSJOBID"" call setpriority 'below normal'";
x "wmic.exe process where processid=""&SYSJOBID"" call setpriority 'idle'";

Tested on SAS 9.4 on Windows 7 and SAS 9.3 on Windows 2008.

I don't have SAS for Linux, but I guess it would look like this to increase the niceness and therefore reduce the process priority:

x "renice -p 16 &SYSJOBID";

Python + GTK creates .dbus-keyrings on Windows

Why Does a Python + GTK Application Create .dbus-keyrings on Windows? I noticed that running a Python + GTK on Windows applicat...