Changeset 5854


Ignore:
Timestamp:
Oct 21, 2008, 6:15:06 PM (16 years ago)
Author:
ole
Message:

Work on caching allowing for instances.
Progress made, but Okushiri example still doesn't cache properly.

Location:
anuga_core/source/anuga/caching
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • anuga_core/source/anuga/caching/caching.py

    r5791 r5854  
    4343#
    4444from os import getenv
     45import types
    4546
    4647import os
     
    258259
    259260  # Create cache directory if needed
    260   #
    261261  CD = checkdir(cachedir,verbose)
    262262
    263263  # Handle the case cache('clear')
    264   #
    265264  if type(func) == types.StringType:
    266265    if string.lower(func) == 'clear':
     
    269268
    270269  # Handle the case cache(func, 'clear')
    271   #
    272270  if type(args) == types.StringType:
    273271    if string.lower(args) == 'clear':
     
    276274
    277275  # Force singleton arg into a tuple
    278   #
    279276  if type(args) != types.TupleType:
    280277    args = tuple([args])
    281278 
    282279  # Check that kwargs is a dictionary
    283   #
    284280  if type(kwargs) != types.DictType:
    285281    raise TypeError   
     
    288284   
    289285  # Hash arguments (and keyword args) to integer
    290   #
    291286  arghash = myhash((args,kwargs))
    292 
     287 
    293288  # Get sizes and timestamps for files listed in dependencies.
    294289  # Force singletons into a tuple.
    295   #
    296 
    297290  if dependencies and type(dependencies) != types.TupleType \
    298291                  and type(dependencies) != types.ListType:
     
    301294
    302295  # Extract function name from func object
    303   #
    304296  funcname = get_funcname(func)
    305297
    306298  # Create cache filename
    307   #
    308299  FN = funcname+'['+`arghash`+']'  # The symbol '(' does not work under unix
    309300
     
    331322 
    332323  # Check if previous computation has been cached
    333   #
    334324  if evaluate:
    335325    Retrieved = None  # Force evaluation of func regardless of caching status.
    336     reason = 4
     326    reason = 5
    337327  else:
    338328    (T, FN, Retrieved, reason, comptime, loadtime, compressed) = \
     
    349339
    350340      # Remove expired files automatically
    351       #
    352341      if options['expire']:
    353342        DeleteOldFiles(CD,verbose)
     
    355344      # Save args before function is evaluated in case
    356345      # they are modified by function
    357       #
    358346      save_args_to_cache(CD,FN,args,kwargs,compression)
    359347
    360348      # Execute and time function with supplied arguments
    361       #
    362349      t0 = time.time()
    363350      T = apply(func,args,kwargs)
     
    369356
    370357      # Save results and estimated loading time to cache
    371       #
    372358      loadtime = save_results_to_cache(T, CD, FN, func, deps, comptime, \
    373359                                       funcname, dependencies, compression)
     
    770756              'No cached result',
    771757              'Dependencies have changed',
    772               'Byte code or arguments have changed',
     758              'Arguments have changed',
     759              'Bytecode has changed',
    773760              'Recomputation was requested by caller',
    774761              'Cached file was unreadable']             
     
    803790                     1: No cached result,
    804791                     2: Dependencies have changed,
    805                      3: Arguments or Bytecode have changed
    806                      4: Recomputation was forced
     792                     3: Arguments have changed
     793                     4: Bytecode has changed
     794                     5: Recomputation was forced
     795                     6: Unreadable file
    807796    comptime --      Number of seconds it took to computed cachged result
    808797    loadtime --      Number of seconds it took to load cached result
     
    889878
    890879  # Check if arguments or bytecode have changed
    891   #
    892880  if compare(argsref,args) and compare(kwargsref,kwargs) and \
    893881     (not options['bytecode'] or compare(bytecode,coderef)):
    894882
    895883    # Arguments and dependencies match. Get cached results
    896     #
    897884    T, loadtime, compressed, reason = load_from_cache(CD,FN,compressed)
    898     ###if T == None and reason > 0:  #This doesn't work if T is a numeric array
    899885    if reason > 0:
    900       return(None,FN,None,reason,None,None,None) #Recompute using same FN
     886      return(None,FN,None,reason,None,None,None) # Recompute using same FN
    901887
    902888    Retrieved = 1
     
    925911    # else:
    926912    #   print 'Match found !'
     913   
     914    # The real reason is that args or bytecodes have changed.
     915    # Not that the recursive seach has found an unused filename
    927916    if not Retrieved:
    928       reason = 3     #The real reason is that args or bytecodes have changed.
    929                      #Not that the recursive seach has found an unused filename
     917      if not compare(bytecode,coderef):
     918        reason = 4 # Bytecode has changed
     919      else:   
     920        reason = 3 # Arguments have changed
     921       
    930922   
    931923  return((T, FN, Retrieved, reason, comptime, loadtime, compressed))
     
    11251117  t0 = time.time()
    11261118  T, reason = myload(datafile,compressed)
    1127   #loadtime = round(time.time()-t0,2)
     1119
    11281120  loadtime = time.time()-t0
    11291121  datafile.close()
     
    12281220        #print 'ERROR (caching): Could not decompress ', file.name
    12291221        #raise Exception
    1230         reason = 5  #(Unreadable file)
     1222        reason = 6  # Unreadable file
    12311223        return None, reason 
    12321224     
     
    12401232      except:
    12411233        #Catch e.g., file with 0 length or corrupted
    1242         reason = 5  #(Unreadable file)
     1234        reason = 6  # Unreadable file
    12431235        return None, reason
    12441236     
     
    12851277      raise MemoryError, msg
    12861278    else: 
    1287       #Compressed pickling     
     1279      # Compressed pickling     
    12881280      TsC = zlib.compress(Ts, comp_level)
    12891281      file.write(TsC)
    12901282  else:
    1291       #Uncompressed pickling
     1283      # Uncompressed pickling
    12921284      pickler.dump(T, file, bin)
    12931285
     
    13281320# -----------------------------------------------------------------------------
    13291321
    1330 def myhash(T):
    1331   """Compute hashed integer from hashable values of tuple T
     1322def myhash(T, ids=None):
     1323  """Compute hashed integer from a range of inputs.
     1324  If T is not hashable being e.g. a tuple T, myhash will recursively
     1325  hash the values individually
    13321326
    13331327  USAGE:
     
    13351329
    13361330  ARGUMENTS:
    1337     T -- Tuple
    1338   """
    1339 
    1340   import types
    1341 
     1331    T -- Anything
     1332  """
     1333
     1334  from types import TupleType, ListType, DictType, InstanceType 
     1335  from Numeric import ArrayType
     1336 
     1337  if type(T) in [TupleType, ListType, DictType, InstanceType]: 
     1338 
     1339      # Keep track of unique id's to protect against infinite recursion
     1340      if ids is None: ids = []
     1341
     1342      # Check if T has already been encountered
     1343      i = id(T)
     1344 
     1345      if i in ids:
     1346          # FIXME (Ole): It seems that different objects get the same id
     1347          # T has been hashed already
     1348       
     1349          #print 'T has already been hashed:', T, id(T)
     1350          return 0
     1351      else:
     1352          #print 'Appending', T, id(T)
     1353          ids.append(i)
     1354   
     1355
     1356  # Start hashing 
     1357 
     1358 
    13421359  # On some architectures None, False and True gets different hash values
    13431360  if T is None:
    1344     return(-1)
     1361      return(-1)
    13451362  if T is False:
    1346     return(0)
     1363      return(0)
    13471364  if T is True:
    1348     return(1)
    1349 
    1350   # Get hash vals for hashable entries
    1351   #
    1352   if type(T) == types.TupleType or type(T) == types.ListType:
    1353     hvals = []
    1354     for k in range(len(T)):
    1355       h = myhash(T[k])
    1356       hvals.append(h)
    1357     val = hash(tuple(hvals))
    1358   elif type(T) == types.DictType:
    1359     val = dicthash(T)
     1365      return(1)
     1366
     1367  # Get hash values for hashable entries
     1368  if type(T) in [TupleType, ListType]:
     1369      hvals = []
     1370      for t in T:
     1371          h = myhash(t, ids)
     1372          hvals.append(h)
     1373      val = hash(tuple(hvals))
     1374  elif type(T) == DictType:
     1375      val = myhash(T.items(), ids)
     1376  elif type(T) == ArrayType:
     1377      val = myhash(tuple(T), ids)
     1378  elif type(T) == InstanceType:
     1379      val = myhash(T.__dict__, ids)
    13601380  else:
    1361     try:
    1362       val = hash(T)
    1363     except:
    1364       val = 1
    13651381      try:
    1366         import Numeric
    1367         if type(T) == Numeric.ArrayType:
    1368           hvals = []       
    1369           for e in T:
    1370             h = myhash(e)
    1371             hvals.append(h)         
    1372           val = hash(tuple(hvals))
     1382          val = hash(T)
     1383      except:
     1384          val = 1
     1385
     1386  return(val)
     1387
     1388# -----------------------------------------------------------------------------
     1389
     1390def compare(A, B, ids=None):
     1391    """Safe comparison of general objects
     1392
     1393    USAGE:
     1394      compare(A,B)
     1395
     1396    DESCRIPTION:
     1397      Return 1 if A and B they are identical, 0 otherwise
     1398    """
     1399
     1400    from types import TupleType, ListType, DictType, InstanceType
     1401   
     1402   
     1403    # Keep track of unique id's to protect against infinite recursion
     1404    if ids is None: ids = {}
     1405
     1406
     1407    # Check if T has already been encountered
     1408    iA = id(A)
     1409    iB = id(B)     
     1410   
     1411    if ids.has_key((iA, iB)):
     1412        # A and B have been compared already
     1413        #print 'Found', (iA, iB), A, B
     1414        return ids[(iA, iB)]
     1415    else:
     1416        ids[(iA, iB)] = True
     1417   
     1418   
     1419    #print 'Comparing', A, B, (iA, iB)
     1420    #print ids
     1421    #raw_input()
     1422   
     1423    if type(A) in [TupleType, ListType] and type(B) in [TupleType, ListType]:
     1424        N = len(A)
     1425        if len(B) != N:
     1426            identical = False
    13731427        else:
    1374           val = 1  #Could implement other Numeric types here
    1375       except:   
    1376         pass
    1377 
    1378   return(val)
    1379 
    1380 # -----------------------------------------------------------------------------
    1381 
    1382 def dicthash(D):
    1383   """Compute hashed integer from hashable values of dictionary D
    1384 
    1385   USAGE:
    1386     dicthash(D)
    1387   """
    1388 
    1389   keys = D.keys()
    1390 
    1391   # Get hash values for hashable entries
    1392   #
    1393   hvals = []
    1394   for k in range(len(keys)):
    1395     try:
    1396       h = myhash(D[keys[k]])
    1397       hvals.append(h)
    1398     except:
    1399       pass
    1400 
    1401   # Hash obtained values into one value
    1402   #
    1403   return(hash(tuple(hvals)))
    1404 
    1405 # -----------------------------------------------------------------------------
    1406 
    1407 def compare(A,B):
    1408   """Safe comparison of general objects
    1409 
    1410   USAGE:
    1411     compare(A,B)
    1412 
    1413   DESCRIPTION:
    1414     Return 1 if A and B they are identical, 0 otherwise
    1415   """
    1416 
    1417   try:
    1418     identical = (A == B)
    1419   except:
    1420     try:
    1421       identical = (pickler.dumps(A) == pickler.dumps(B))
    1422     except:
    1423       identical = 0
    1424 
    1425   return(identical)
     1428            identical = True
     1429            for i in range(N):
     1430                if not compare(A[i], B[i], ids):
     1431                    identical = False; break
     1432               
     1433    elif type(A) == DictType and type(B) == DictType:
     1434        if len(A) != len(B):
     1435            identical = False
     1436        else:   
     1437            identical = True
     1438            for key in A.keys():
     1439                if not B.has_key(key):
     1440                    identical = False; break
     1441         
     1442                if not compare(A[key], B[key], ids):
     1443                    identical = False; break     
     1444   
     1445    elif type(A) == type(B) == types.InstanceType:   
     1446        # Take care of special case where elements are instances           
     1447        # Base comparison on attributes
     1448               
     1449        a = A.__dict__                 
     1450        b = B.__dict__                             
     1451               
     1452        identical = compare(a, b, ids)               
     1453
     1454   
     1455       
     1456    else:       
     1457        # Fall back to general code
     1458        try:
     1459            identical = (A == B)
     1460        except:
     1461            try:
     1462                identical = (pickler.dumps(A) == pickler.dumps(B))
     1463            except:
     1464                identical = 0
     1465
     1466    # Record result of comparison and return           
     1467    ids[(iA, iB)] = identical
     1468   
     1469    return(identical)
    14261470
    14271471# -----------------------------------------------------------------------------
     
    23392383      argstr = argstr + "'"+str(args)+"'"
    23402384    else:
    2341       #Truncate large Numeric arrays before using str()
     2385      # Truncate large Numeric arrays before using str()
    23422386      import Numeric
    23432387      if type(args) == Numeric.ArrayType:
  • anuga_core/source/anuga/caching/dummy_classes_for_testing.py

    r5853 r5854  
    1010    def copy(self):
    1111        return Dummy(self.value, self.another)
     12       
     13    def __repr__(self):
     14        return str(self.value) + ', ' + str(self.another)
    1215   
    1316
  • anuga_core/source/anuga/caching/test_caching.py

    r5853 r5854  
    3535  return A.value+B.value, A.another+B.another
    3636 
    37  
     37
     38def f_generic(A):
     39  return A
    3840 
    3941def clear_and_create_cache(Dummy, verbose=False):
     
    114116           
    115117
     118           
    116119    def test_caching_of_numeric_arrays(self):
    117120        """test_caching_of_numeric_arrays
     
    190193
    191194            # Retrieve
     195            #T2 = cache(f_object, (A1, B1),
     196            #           compression=comp, verbose=verbose)                       
     197                       
     198            # Retrieve
    192199            T2 = cache(f_object, (A1, B1),
    193200                       compression=comp, test=1, verbose=verbose)
     
    205212           
    206213
    207                        
     214    def test_caching_of_circular_structures(self):
     215        """test_caching_of_circular_structures
     216       
     217        Test that Caching doesn't recurse infinitely in case of
     218        circular or self-referencing structures
     219        """
     220       
     221        verbose = False
     222       
     223        # Create input argument
     224        A = Dummy(5, 7)
     225        B = {'x': 10, 'A': A}
     226        C = [B, 15]
     227        A.value = C # Make it circular
     228       
     229
     230        # Test caching
     231        comprange = 2
     232        for comp in range(comprange):
     233 
     234            # Evaluate and store
     235            T1 = cache(f_generic, A, evaluate=1,
     236                       compression=comp, verbose=verbose)
     237
     238            # Retrieve
     239            T2 = cache(f_generic, A,
     240                       compression=comp, test=1, verbose=verbose)
     241                       
     242            # Check for presence of cached result
     243            msg = 'Cached object was not found'           
     244            assert T2 is not None, msg
     245
     246            # Reference result
     247            T3 = f_generic(A) # Compute without caching
     248
     249           
     250            msg = 'Cached result does not match computed result'
     251            assert str(T1) == str(T2), msg
     252            assert str(T2) == str(T3), msg
     253           
     254                                   
     255           
     256           
     257           
     258
     259    def XXtest_caching_of_simple_circular_structures(self):
     260   
     261        # FIXME (Ole): This one recurses infinitly on
     262        # arg strings.
     263       
     264        """test_caching_of_circular_structures
     265       
     266        Test that Caching doesn't recurse infinitely in case of
     267        circular or self-referencing structures
     268        """
     269       
     270        verbose = True
     271       
     272        # Create input argument
     273        A = {'x': 10, 'B': None}
     274        B = [A, 15]
     275        A['B'] = B # Make it circular
     276       
     277        print A
     278
     279        # Test caching
     280        comprange = 2
     281        for comp in range(comprange):
     282 
     283            # Evaluate and store
     284            T1 = cache(f_generic, A, evaluate=1,
     285                       compression=comp, verbose=verbose)
     286                       
     287            import sys; sys.exit()
     288                       
     289
     290            # Retrieve
     291            T2 = cache(f_generic, A,
     292                       compression=comp, test=1, verbose=verbose)
     293                       
     294            # Check for presence of cached result
     295            msg = 'Cached object was not found'           
     296            assert T2 is not None, msg
     297
     298            # Reference result
     299            T3 = f_generic(A) # Compute without caching
     300
     301
     302            assert T1 == T2, 'Cached result does not match computed result'
     303            assert T2 == T3, 'Cached result does not match computed result'
     304           
     305                                   
    208306           
    209307           
     
    429527      This test shows how instances can't be effectively cached.
    430528      myhash uses hash which uses id which uses the memory address.
     529     
     530      This will be a NIL problem once caching can handle instances with different id's and
     531      identical content.
     532     
     533      The test is disabled.
    431534      """
     535     
     536
     537     
    432538      verbose = True
    433539      #verbose = False
     
    526632         
    527633# Cache created for use with 'test_objects_are_created_memory'
    528 initial_addr = `Dummy_memorytest`
    529 clear_and_create_cache(Dummy_memorytest, verbose=False)
    530  
    531      
    532 
    533 
    534 
    535      
    536        
     634#initial_addr = `Dummy_memorytest`
     635#clear_and_create_cache(Dummy_memorytest, verbose=False)
     636 
    537637
    538638
    539639#-------------------------------------------------------------
    540640if __name__ == "__main__":
    541     suite = unittest.makeSuite(Test_Caching,'test')
     641    suite = unittest.makeSuite(Test_Caching, 'test')
    542642    runner = unittest.TextTestRunner()
    543643    runner.run(suite)
Note: See TracChangeset for help on using the changeset viewer.