Thursday, January 30, 2014

Magic wand in Performance tuning - lazy instantiation

If you are in the business of programming for some time,  you likely know “the faster, the better” when it comes to the performance of the programming.

Take an example, in Microsoft store app certification requirement, it stated that app should response to any user action within 2 seconds in X86 or X64 and within 5 seconds in ARM architecture. and Microsoft has a certification verification kit to verify your app. Any app failed in this requirement would not get to the store.

You may also know that “The less is better” when it comes to line of code of the programming. if you can get the job done in 100 line, you would not want to make it to 110. The statistical study indicates that the more the lines of code, the more bugs it will be and the higher the maintained cost.

In most of cases, we programmers are fighting between these 2 goals. because in most of cases, faster performance means more code, but more code also mean longer execution time…  this is an art of balancing.

Take a look at this block of code in CustomerBO:

        Public Function GetCustomerDemographicInfo(ByVal customerID As Long, ByVal transactionID As Long, ByVal isTxnSummary As Boolean) As CustomerDemographicBE
            Dim custDemographic As CustomerDemographicBE = Nothing
            Dim customerDAO As CustomerDAO
            customerDAO = New CustomerDAO
            custDemographic = New CustomerDemographicBE
            custDemographic = customerDAO.GetCustomerDemographicInfo(customerID, transactionID, isTxnSummary)
            Return custDemographic
        End Function

CusotmerBO will surely interact with CustomerDAO quite often.  this looks like you are doing restaurant business and you hire one chef to cook one dish and fire him then hire another chef to cook another dish… There is lot of effort spent in hiring and firing. ( instantiation and  discard). In term of Line Of Code, you will see this line “Dim customerDAO As CustomerDAO = New CustomerDAO”  in almost every methods in the class.

As the rule says “ the less the better” and also out of laziness, you want to write less code, right? so you want to instantiate the class once and use it everywhere. so you thought of a perfect place to do this. “Constructor” . OK, you put this line of code in constructor and use it in all methods within the class.  The modified code will be similar to the following:

Public Sub New()
    mCustomerDAO = New CustomerDAO()
End Sub

and

Public Function GetCustomerDemographicInfo(ByVal customerID As Long, ByVal transactionID As Long, ByVal isTxnSummary As Boolean) As CustomerDemographicBE
            Dim custDemographic As CustomerDemographicBE = Nothing
            custDemographic = New CustomerDemographicBE
            custDemographic = mCustomerDAO.GetCustomerDemographicInfo(customerID, transactionID, isTxnSummary)
            Return custDemographic
End Function


If you do that in all methods within a class and do that for all BO classes, your total line of code will be around 5%  ( in the size of our code base total size of 800K, it likely mean 40K LOC reduction) and in most of case, your performance will be improved…

well, I made this statement is based on the assumption that you have a very good object model and there is in no time when an object is instantiated but never be used.  It turns out that this is a huge assumption to be made. In most of cases it is turns out to be a false assumption. This is especially the case  in our codebase. Take a look at this constructor :

    Public Sub New()
            mSelfAndMedicalCertificationBO = New SelfAndMedicalCertificationBO()
            mVehicleBO = New VehiclesBO
            mPlacardBO = New DisabilityPlacardBO()
            mDrivingLicenseBO = New DrivingLicenseBO()
            mTransactionDAO = New TransactionDAO()
            mPlateBO = New PlateBO
            customerDAO = New CustomerDAO()
            mDeficiencyDao = New DeficiencyDAO
            'objDrivingLicense = New DrivingLicenseDAO()
            mInterfaceBO = New InterfaceBO()
            mOrganDonorVoterRegistrationDAO = New OrganDonorVoterRegistrationDAO()
            mRegistrationBO = New RegistrationBO
            mCustomerBO = New CustomerBO()
            mOwnershipBO = New OwnerShipBO
            mTitleBO = New TitleBO
            mFlashDAO = New FlashDAO
            mFlashBO = New FlashBO
            mOfficeBO = New OfficeBO
            mPlateDAO = New PlateDAO
            mPaymentProcessingBO = New PaymentProcessingBO
            mRegistrationDAO = New RegistationDAO
            mDriverRecordSaleDAO = New DriverRecordSaleDAO
            mLienBO = New LienHolderBO
            mCustomerIDsCollector = New TwoKeyDictionary(Of String, Long, Long)
            mCommonReplicationBO = New CommonReplicationBO
            mOutputDocumentLibraryDAO = New OutputDocumentLibraryDAO
        End Sub



You might guess it right, it is for TransactionBO. for these who are familiar with our codebase, you know this class is a “knows all , does all” class. it handles all transactions of all types, from driver to vehicle, from living customer to deceased customer. from BAM transaction to legacy transaction; from interface to interactive…  put it in this way “ if you take this class out of the system, NOTHING will work.”  You can tell how big it is by looking at the LOC of this class, 16.7K!  It is as big as the size of a medium size system. it will never be a bad idea to break this class to smaller ones. For example, make one class handle Title transactions, and make another class handle DrivingLicense transactions . and only keep these common functionality  in TransactionBO and make it the base class for all others. Before we do that, we will see lots of objects been instantiated but never be used. ( what a waste!   from both performance and line of code point of views).

well, the short term fix could be lazy instantiate. (while I am proposing this temporary fix, I am still calling object structure redesign for TransactionBO, TransactionBPC and TransactionDAO, because the fundamental problems of a huge class handles all still exist and need to be dealt with.)

This is how to make it work.

1) remove substantiation statements in the construction for these objects may or may not be used in the class. ('mCustomerDAO = New CustomerDAO  or  Dim 'mCustomerDAO As New CustomerDAO  )
2) for these objects define another private member like this way:
Private _CustomerDAO As CustomerDAO
3) then change the original mCustomerDAO to make it a read only property

 Private ReadOnly Property mCustomerDAO() As CustomerDAO
            Get
                If _CustomerDAO Is Nothing Then
                    _CustomerDAO = New CustomerDAO()
                End If
                Return _CustomerDAO
            End Get
End Property

After that, all rest of the code stay as they are. That’s all it takes to implement lazy instantiation.

what is the performance implication ?

Before I recommend this pattern, I tried it with my POC, with this pattern, I managed to reduce response time of  my window store app from 15 seconds to less than 2 second when I only implemented it for one of UI class MediaElement. For my case, it is not that I instanced the object without using it. it is rather my strategy to distribute the substantiation time among different clicks. When User click on <lesson>  button to go to Lesson page, I hold off the instantiation of the MediaElement object within Lesson page. When user first time click on <Listen> button, I then  instantiate the MediaElement and then use the object make speech call.

when my  colleague  implemented  my recommendation in TransactionBPC , the performance test result is shown below:

Methods (before Fix) (after Fix)
TransactionBPC.GetOfficeHolidays() 12969 ms 1000 Iterations 3143 ms 1000 Iterations
TransactionBO.GetOfficeHolidays() 2136 ms 1000 Iterations 2177 ms 1000 Iterations
TransactionDAO.GetOfficeHolidays() 1922 ms 1000 Iterations 2108 ms 1000 Iterations

 The performance improvement in BPC and BO, DAO is more than 75%. while there is no noticeable change in BO or DAO


The reason I call it Magic Wand are
1) it does not take much effort, nor risk.
2) the pay back is great.
3) it is still a magic like getting a rabbit from a top hat. The real work of growing the rabbit still needed. like this case, to spend time to break the class to smaller ones is still a valid call from architecture point of view.


thanks and hope you enjoy your programming work as I do…

No comments:

Post a Comment