Since I'm most familiar with 10.1+, and cursors in general are a lot better in the `da` module, here's a potential solution. Currently, you are creating a cursor each time you change fields, which means you are taking a hit there. Furthermore, you are checking the field type for each record instead of just using the field type once to filter initially.

I've changed how `Null` values are checked, but I haven't thoroughly tested it to check for all possible values. For the small sample dataset I had, it worked @ 10.2.2.

    #Return None if the value needs to be changed, else return the value
    def nullify(value):
        x = value    
        if value is not None: #True null fields are read as None types
            if type(value) == str:
                if value.lstrip(' ') == '' or value.lower() == '<null>':
                    x = None
            else: 
                if value == 0:
                    x = None 

    return x

----------

    #We're only interested in some fields
    ftypes = ("String", "SmallInteger", "Integer", "Double")
    fieldList = [f.name for f in arcpy.ListFields(fc) if f.type in ftypes]
    
    with arcpy.da.UpdateCursor(fc, fieldList) as rows:
        for row in rows:
            vals = [r for r in row]
            nulled = map(nullify, vals)
            if vals != nulled: #Only update if the row actually needs to be changed.
                rows.updateRow(nulled)