Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 58 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
58
Dung lượng
244,38 KB
Nội dung
Miscellaneous Topics 439 } if(m_Vol <= 0.0) return false; double d1 = log(m_Fwd / m_Strike) / m_vRootT + m_vRootT / 2.0; double N1 = ndist(d1); // ndist(x) = N(x) double N2 = ndist(d1 - m_vRootT); m_Call = m_Fwd * N1 - m_Strike * N2; m_Put = m_Call + m_Intrinsic; m_Straddle = m_Call + m_Put; if(!calc_derivs) return true; // Hedge calculations assume constant volatility double n1 = n(d1); // n = dN(x)/dx m_PutDelta = (m_CallDelta = N1) - 1.0; m_Gamma = n1 / m_vRootT / m_Fwd / m_Vol; m_Vega = n1 * m_vRootT * m_Fwd; return true; } The following XLL-exportable function shows a simple example of its use. It is also used in the example implementation of CMS derivative pricing (see section 10.11 on page 513). xloper * __stdcall BlackOpt(double t_exp, double fwd, double strike, double vol) { BlackOption Opt(t_exp, fwd, strike, vol); Opt.Calc(true); // true: calc greeks #define NUM_BLACK_RTN_VALS 6 double ret_vals[NUM_BLACK_RTN_VALS] = { Opt.GetCallPrice(), Opt.GetPutPrice(), Opt.GetCallDelta(), Opt.GetPutDelta(), Opt.GetGamma(), Opt.GetVega()}; // Return a single row array cpp_xloper RetVal((RW)1, (COL)NUM_BLACK_RTN_VALS, ret_vals); return RetVal.ExtractXloper(); } C++ class construction iteratively (and implicitly) calls the constructor of any contained class. Consider the following example: class A { public: A() {m_iVal = 0;} A(int i) {m_iVal = i;} void SetVal(int i) {m_iVal = i;} int GetVal(void) const {return m_iVal;} 440 Excel Add-in Development in C/C++ private: int m_iVal; }; class B { public: B(int i) {m_Ainstance.SetVal(i);} int GetVal(void) const {return m_Ainstance.GetVal();} private: A m_Ainstance; }; class C { public: C(int i) : m_Ainstance(i) {} int GetVal(void) const {return m_Ainstance.GetVal();} private: A m_Ainstance; }; When an instance of class B is created, the contained instance of class A’s member variable m_iVal is first initialised to zero in an implicit call to A’s default constructor, and then is reset with an explicit call to SetVal(). In class C, on the other hand, A’s second constructor is called explicitly and m_iVal is set only once. This is a trivial example of something that experienced C++ programmers will be well aware of, but with more complex objects such things can have a significant impact on performance. Also on the subject of minimising constructor and destructor calls, classes should always be passed and returned by reference (or by pointer). Passing or returning by value causes the implicit creation, copying and destruction of temporary class instances. You should also avoid calling into Excel through the C API for functions that are also available in standard string or mathematics libraries, for example. This is because the overhead of calling into Excel is significant. You can break this rule where you are not calling back into Excel very often, or where you need compatible behaviour with the worksheet functions. There are many other ways in which code can leak performance, such as the inefficient use of arrays, for example. These are too numerous to go into in great detail, suffice to say that there is still good reason to write nice code. 9.14.2 VBA code optimisation Given that VBA is much slower than compiled C++, it is likely that at some point you will find there is a performance bottleneck in your VBA code. The highly-recommended Professional Excel Development 10 contains a chapter specifically about the optimisation of VBA. The following list reproduces some of the optimisations they recommend, although there is a great deal more said in their text. 10 Bullen, Bovey and Green, 2005, Addisson Wesley. Miscellaneous Topics 441 • Use matching data types to avoid implicit conversions, and use Variants only where necessary. • Perform explicit type conversions CStr(), CDbl(), etc., instead of VBA’s implicit conversions. • Use Len(string)= 0 instead of string= "" to detect zero length/empty strings. • Use string-typed string functions instead of Variant functions, e.g., use Left() instead of Left(). • Pass strings and arrays ByRef instead of ByVal. • Use Option Compare Text to make string comparisons case-sensitive by default, using the CompareMethod of StrComp() when case-insensitivity is needed. • Avoid late binding by declaring object variables as their explicit types instead of As Object . • Use integer division instead of floating point division (\ instead of /) where integer arguments are being evaluated to an integer. • Iterate collections using For Each instead of For Next and indexing. • Iterate arrays using For Next and indexing instead of For Each. • Use If bVariable Then instead of If bVariable = True Then • Use If Then ElseIf Then instead of IIF() or Select Case • Use With End With blocks wherever possible. There may be times when you feel the readability of your code is improved with one of the less-efficient ways of doing things. Select Case for example leads to very readable maintainable code, so you might justifiably prefer it. 9.14.3 Excel calculation optimisation Optimising the calculation time of an Excel spreadsheet is a far more complex topic than the code optimisations discussed briefly above. There are many more factors that can affect the perceived amount of time spent in recalculation: • System resource availability and performance (memory, processor, disk and network access time, multi-threading settings in Excel 2007, etc.) • Workbook complexity (inter-workbook and inter-worksheet links, number of cells con- taining formulae, display complexity, etc.) • Worksheet formula choices (use of volatile functions, use of inefficient functions) • XLL add-in and user-defined function performance • VBA user-defined function performance • COM add-in function performance Understanding the first point, resource availability, is key to knowing how much effort to put into resolving the others. A system that’s low on memory or that makes frequent access to a remote system via a slow network connection, or a remote system that is struggling to keep up, will not be helped much by increasing the speed of execution of an add-in function. Equally, where a simple and cheap upgrade of hardware will restore the recalculation time expected by the user, relatively costly development hours are probably better not spent on optimisation. With Excel 2007, the purchase of a second processor or a dual-core machine could reduce calculation times by up to half. 442 Excel Add-in Development in C/C++ Multi-threading in Excel 2007 can also improve performance on a single processor machine where a UDF makes a call to a remote calculation server (or server farm or cluster) where the server has many processors. This enables the single processor machine to send many requests off to the server roughly at the same time, fully loading the server, rather than having to wait for each calculation request to complete before sending the next. Even given the often better alternatives to re-coding, you would hope not to end up in a situation where you have to perform emergency optimisation on your workbooks and projects. Following sound principles from the outset, performance should only deteriorate slowly as new features/code/cells are added. However, there are times when things can get a little sluggish and the rest of this section aims to provide some practical advice on what to do. Many of the previous chapters and sections of this book refer to performance. In fact, given that the use of XLLs is largely a performance-driven choice, you could say that this entire book is about improving performance. So rather than restating all that has been said already, this section refers back to those sections and adds additional advice where necessary. There is one important point that applies to all programming optimisation, and that is to avoid, or at least be aware of, implicit type conversions. When designing the interfaces for your XLL worksheet function exports, and you need to get best possible performance, it is better to register all of your arguments as value xloper/xloper12s (type P/Q) as this avoids the overhead of Excel conversion. You do, of course, then need to check that the types in your code are correct, and convert them or fail, depending on how exacting you are with the caller. Before getting to that, there is some ancient Excel-lore that states that you should try to arrange your calculations on the sheet such that dependencies run from top- left to bottom-right. For example, B2 depending on A1 is good; vice versa is bad. Whilst this might have been good advice in some past version of Excel (or perhaps some other spreadsheet package) the author can find nothing to suggest this will improve performance. The two workbooks TopLeftDrivenCalcTest.xls and BottomRightDrivenCalcTest.xls contain named ranges of 50,000 cells, contain- ing volatile formulae, that are recalculated 1,000 times under VBA macro control. As you can verify for yourself, the recalculation times are as good as identical. Section 2.12 Excel recalculation logic, page 33, covers the differences between the way Excel 97 and 2000 differ from versions 2002+ with respect to the recalculation of inter-worksheet (not inter-workbook) links. If you or your users are or might be using 97 or 2000 you should be aware of the recalculation problems that careless use of such links can cause and follow the advice in that section. The section also covers the use and mis-use of volatile functions, another potential performance black hole, as well as data tables, where completion of recalculations may not always be obvious, as well as being slow. Section 2.16 Good spreadsheet design and practice, page 49, covers some basic ideas relating to the choice of worksheet function that you make. In particular it discusses formula repetition and using MATCH() and INDEX() instead of VLOOKUP().Thereare myriad examples that could be thought up of two or more ways to use Excel’s own functions to do the same thing, for example: =IF(INT(A1)= 1,C1,IF(INT(A1)= 2,C2,IF(INT(A1)= 3,C3,# VALUE!))) =CHOOSE(A1,C1,C2,C3) =INDEX(C1:C3,A1) Miscellaneous Topics 443 These 3 expressions all do almost the same thing. (The third will return a #REF! error if A1¿3 and INDEX(C1:C3,1.999999761466)will round up to 2 and return C2, whereas 1.999999761465 will round down). Clearly, without some good reason for choosing the first, the second and third are far more succinct, readable and efficient. It is amazing that people struggle with the limitation of the number of nested IF()s that Excel allows ([v11–]: 7, [v12+]: 64) and the ingenious work-arounds, such as splitting more nested IF()s across more than one cell. If you need more than 3 or 4 in a single expression you should certainly be considering something like CHOOSE or INDEX instead, for reasons of readability, if not performance. Much of the skill of knowing which combination of Excel’s own functions to best use can only come with experience, although the general advice is that keeping things simple and readable will automatically keep them efficient in most cases. This is explained further in section 2.12.8 on page 41, which covers argument evaluation in functions that condi- tionally ignore some of their arguments, for example, the functions IF(), OR(), AND() and CHOOSE(): All arguments are recalculated prior to Excel calling the function, regardless of whether or not the result will be ignored. This clearly has an impact on recalculation time and the section’s advice of using only simple arguments, even if that means refer- ences to cells containing complex expressions, should always be followed. This is even true to the extent that an expression like =B5*IF(C6,1,±1) is slightly more efficient than =IF(C6,B5,±B5), since in the first example B5 is only dereferenced once. Once you are happy that any sluggishness cannot easily be removed by optimising add-in code (after all, you may only have Excel formulae in your workbook), and you have scoured the workbook for inefficient formulae, you must turn to fine-tuning what Excel does and does not calculate. In a large workbook, you may have sheets that do not need to be calculated when the sheet is not being viewed. In other words you may want to turn off a sheet’s calculation unless it is active. This can make a huge difference to a large workbook, and fortunately there are a couple of ways to achieve this. An example of such a sheet might be one that shows a detailed breakdown of something that you ordinarily do not need to see, or that calculates and displays data graphically. The Excel.Worksheet object exposes a property EnableCalculation whose default state is True and can be switched off in a VBA macro or with a call via COM. The most straightforward approach is to place a couple of event traps in the worksheet’s VBA code module, as shown here: Private Sub Worksheet_Activate() EnableCalculation = True End Sub Private Sub Worksheet_Deactivate() EnableCalculation = False End Sub This will have the effect of stopping calculation except when active. On activation, the setting of the property from False to True triggers Excel to recalculate the sheet, so that, from the user’s point of view, data will always seem up-to-date (once recalculation is complete). The following event will cause the sheet to be updated once only when it becomes active, but then to be static. There may not be many times when you can get away with this or need to do it, however. 444 Excel Add-in Development in C/C++ Private Sub Worksheet_Activate() EnableCalculation = True EnableCalculation = false End Sub Given that you can do these things, you should be designing your workbook to maximise the number of sheets that you can do this with. This means sketching out the function of each sheet, the dependencies, and the drivers of calculations. For example, suppose you want to create a simple workbook that pulls in real-time market data and prices a small portfolio of derivatives. A sensible hierarchy of worksheets might look something like this: Worksheet Notes Config EnableCalculation: always true Processes configuration data that is used when building curves, pricing positions, etc. Calculations are driven by changes to configuration data and a master non-volatile trigger (could be today’s date). Curves EnableCalculation: always true Contains all links to external dynamic real-time data. Processes data for use in pricing, and increments triggers for dependent calculations. Portfolio EnableCalculation: true when active only, or always true (see note) Calculates present value of position in the portfolio using configuration data from Config and market data from Curves. Note: Provided other sheets that need real-time updates do not link to this sheet, this can be recalculated when active only. Risk EnableCalculation: true when active only Calculates portfolio risk and display graphs. Depends on volatile curve data and non-volatile data from Portfolio sheet, i.e., does not depend on Portfolio being recalculated when curves change. xCashflow EnableCalculation: true when active only Calculate and display actual and anticipated cashflows. Note that xCashflow is so named to ensure that it is after Config and Curves alphabetically, for the benefit of those running the workbook in versions 97 and 2000. You may need to go one step further, and be more selective in what you let be recal- culated on a given sheet. For example, you may have some cells that drive calculations in other sheets, but other related cells that take a long time to recalculate but do not drive such things. In this case you would ideally like to be able to tell a function to recalculate only when it is on the active sheet. As such functions are likely to be either Miscellaneous Topics 445 VBA user-defined functions or XLL functions, the question is then how can a function determine this. In both these cases the trick is to find out where the function is being called from: is it a worksheet cell or range of cells; are they on the active sheet; and so on. In VBA this is done using Application.Caller. This example returns an incremented counter if called from the active sheet and returns zero otherwise: Option Explicit Dim count As Integer Function CallerIsActive() As Boolean Dim r As Range With Application If IsObject(.Caller) Then Set r = .Caller CallerIsActive = (r.Worksheet.Index = ActiveSheet.Index) Set r = Nothing Exit Function End If End With CallerIsActive = False End Function Function ExampleActiveOnly(trigger As Integer) As Variant If CallerIsActive() Then count = count + 1 ExampleActiveOnly = count Else ExampleActiveOnly = 0 ' return default value End If End Function Note that the function CallerIsActive compares the Worksheet.Index property rather than the .Name property, a much faster operation and safe within the context of a function whose scope is this workbook. Note also the explicit use of a Range object to ensure early binding of the .Worksheet property. Though the saving may be small, it’s good practice. The statement Set r = Nothing in theory ought not to be necessary as VBA’s garbage collector should free any resources associated with the reference. However, this may not always happen as it should so it is good practice to do this explicitly all the same. You should remember that all this check saves you is the recalculation time of the given function. It does not prevent dependents being recalculated. One limitation of doing this with VBA is that it cannot return the last value(s) of the calling cell(s). The statement ExampleActiveOnly = Application.Caller.Value2, apart from its assumption that Caller is a Range object, would lead Excel to complain of a circular reference. This is unfortunate, as this is precisely what you would like to be able to do: return the old value so that the dependent values do not change to something invalid. Fortunately the C API permits XLL functions registered as macro-sheet equivalent to read the calling cell’s old value, as shown in the following code. This does essentially the same thing as the above VBA code but with the added benefit of returning the caller’s last value if not on the active sheet. 446 Excel Add-in Development in C/C++ xloper * __stdcall ExampleActiveOnly(xloper *pTrigger) { static count = 0; // could be incremented by more than one thread cpp_xloper Op; Op.Excel(xlfCaller); // Set Op = caller's reference if(Op.IsActiveRef()) Op = ++count; // re-use Op for the return value else // return the last value Op.ConvertRefToValues(); // fails if not registered as type # return Op.ExtractXloper(); } The cpp_xloper’s member function Excel() calls Excel4v()/Excel12v(),and sets a flag to tell the class to use xlFree to free the memory. (xloper/xloper12s of type xltypeRef point to allocated memory). The code for IsActiveRef(), listed below, uses the C API-only function xlSheetId to obtain the ID of the active sheet. Note that this ID is not thesameasthe.Index property from the above VBA example, which is simply the index in the workbook’s collection of sheets. // Is the xloper a reference on the active sheet? bool cpp_xloper::IsActiveRef(void) const { DWORD id; if(gExcelVersion12plus) { if(m_Op12.xltype == xltypeSRef) // then convert to xltypeRef { xloper12 as_ref = {0, xltypeNil}; xloper12 type = {0, xltypeInt}; type.val.w = xltypeRef; Excel12(xlCoerce, &as_ref, 2, &m_Op12, &type); if(as_ref.xltype != xltypeRef) return false; id = as_ref.val.mref.idSheet; Excel12(xlFree, 0, 1, &as_ref); } else if(m_Op12.xltype == xltypeRef) id = m_Op12.val.mref.idSheet; else return false; xloper12 active_sheet_id; if(Excel12(xlSheetId, &active_sheet_id, 0) || active_sheet_id.xltype != xltypeRef || id != active_sheet_id.val.mref.idSheet) { // No need to call xlFree: active_sheet_id's xlmref pointer is NULL return false; } } else { if(m_Op.xltype == xltypeSRef) // then convert to xltypeRef Miscellaneous Topics 447 { xloper as_ref = {0, xltypeNil}; xloper type = {0, xltypeInt}; type.val.w = xltypeRef; Excel4(xlCoerce, &as_ref, 2, &m_Op, &type); if(as_ref.xltype != xltypeRef) return false; id = as_ref.val.mref.idSheet; Excel4(xlFree, 0, 1, &as_ref); } else if(m_Op.xltype == xltypeRef) id = m_Op.val.mref.idSheet; else return false; xloper active_sheet_id; if(Excel4(xlSheetId, &active_sheet_id, 0) || active_sheet_id.xltype != xltypeRef || id != active_sheet_id.val.mref.idSheet) { // No need to call xlFree: active_sheet_id's xlmref pointer is NULL return false; } } return true; } Excel 2007 multi-threading note: Excel 2007 regards functions registered as macro-sheet equivalents, type #, as thread-unsafe. This prevents ExampleActiveOnly() being registered as type $ in Excel 2007. You may also like to create worksheet functions that are only recalculated, say, when a button on the active sheet is pressed. One way to achieve this is to create functions that take an argument, perhaps optional, where the functions only recalculate when that argument is TRUE, and otherwise return the cell’s last value. Again, using VBA this is not possible. Using the C API this is straightforward, as the following function demonstrates. xloper * __stdcall ExampleRecalcSwitch(xloper *pArg, xloper *pDontRecalc) { cpp_xloper Op, DontRecalc(pDontRecalc); if(DontRecalc.IsTrue()) // then return the last value { Op.SetToCallerValue(); } else // recalculate { Op = pArg; Op = process_arg((double)Op); } return Op.ExtractXloper(); } 448 Excel Add-in Development in C/C++ bool cpp_xloper::SetToCallerValue(void) { Free(); if(gExcelVersion11minus) { // Get a reference to the calling cell(s) xloper caller; if(Excel4(xlfCaller, &caller, 0) != xlretSuccess) return false; if(!(caller.xltype & (xltypeRef | xltypeSRef))) { Excel4(xlFree, 0, 1, &caller); return false; } // Get the calling cell's value if(Excel4(xlCoerce, &m_Op, 1, &caller) != xlretSuccess) { Excel4(xlFree, 0, 1, &caller); return false; } return m_XLtoFree = true; } else { // Get a reference to the calling cell(s) xloper12 caller; if(Excel12(xlfCaller, &caller, 0) != xlretSuccess) return false; if(!(caller.xltype & (xltypeRef | xltypeSRef))) { Excel12(xlFree, 0, 1, &caller); return false; } // Get the calling cell's value if(Excel12(xlCoerce, &m_Op12, 1, &caller) != xlretSuccess) { Excel12(xlFree, 0, 1, &caller); return false; } return m_XLtoFree12 = true; } } Without using the cpp_xloper class, the above code could be implemented as follows in a function registered as type #. xloper * __stdcall ExampleRecalcSwitch(xloper *pArg, xloper *pDontRecalc) { // Not thread-safe, but this function must be registered as type # // so cannot also be registered as thread-safe in Excel 12 static xloper ret_val; if(pDontRecalc->xltype == xltypeBool && pDontRecalc->val.xbool == 1) { [...]... // Core #define #define #define function NDINV_ITER_LIMIT NDINV_EPSILON NDINV_FIRST_NUDGE 473 50 1e-12 // How precise do we want to be 1e-7 // Minimum change in answer from one iteration to the next #define NDINV_DELTA 1e-10 // Approximate working limits of Excel' s NORMSINV() function in Excel 2000 #define NORMSINV_LOWER_LIMIT 3.024e-7 #define NORMSINV_UPPER_LIMIT 0 .99 999 9 void ndist_inv(double prob,... underlying variable (and its inverse) Excel provides four built -in functions: NORMDIST(), NORMSDIST(), NORMINV() and NORMSINV() In version 9 (Excel 2000) and earlier there are a number of serious problems with the working ranges and accuracy of these functions: • The inverse functions are not precise inverses; • The range of probabilities for which NORMSINV() works is roughly 3.024e-7 to 0 .99 999 9; •... reasonable approximation that is more accurate in the tails than the Taylor series implemented above It also appears to be the basis of Excel s own functions for versions 2000 and earlier #define #define #define #define #define #define #define B1 0.3 193 8153 B2 -0.356563782 B3 1.78147 793 7 B4 -1.82125 597 8 B5 1.3302744 29 PP 0.23164 19 NEG_LN_ROOT_2PI -0 .91 893 8533204673 double cndist(double d) { if(d ==... persistent structure in the add -in, it is advisable to link its existence to the cell that created it, so that deletion of the cell’s contents, or of the cell itself, can be used as a trigger for freeing the resources used This also enables the return value for the sequence to be passed as a parameter to a worksheet function 470 Excel Add -in Development in C/C++ (See sections 9. 6 Maintaining large data... if(!wcschr(search_text, *t)) // *t not in search_text: return posn return 1 + t - text; return 0; // all of text chars are in search_text (but not vice versa) } Example Add-ins and Financial Applications 457 // Excel 11- interface function Uses xlopers and byte-string size_t stdcall find_first_excluded_xl4(char *text, char *search_text) { return find_first_excluded(text, search_text); } // Excel 12+ interface function... 0; } size_t find_first(wchar_t *text, wchar_t *search_text) { 456 Excel Add -in Development in C/C++ if(!text | | !search_text) return 0; wchar_t *p = wcspbrk(text, search_text); return p ? 1 + p - text : 0; } // Excel 11- interface function Uses xlopers and byte-string size_t stdcall find_first_xl4(char *text, char *search_text) { return find_first(text, search_text); } // Excel 12+ interface function... commas for stops and stops for commas things are more complex (You could do this in 452 Excel Add -in Development in C/C++ three applications of SUBSTITUTE(), but this is messy.) Writing a function in C that does this is straightforward (see replace_mask() below) The C and C++ libraries both contain a number of low-level string functions that can easily be given Excel worksheet wrappers This section presents... drawback is the limited error handling: any error in input is reflected with return values of 0 #define PI 3.141 592 6535 897 9323846264 void generate_BM_pair(double &z1, double &z2) { // Use Excel 2003' s algorithm to generate std random numbers // More reliable than earlier Excel algorithms and calling // this implementation of it is much faster than calling // back into Excel with xlfRand double r1 = rand_xl2003.get();... norm_dist_inv_xl4 or norm_dist_inv_xl12 (exported) NdistInv (registered with Excel) Description Returns the inverse of N (x) consistent with the ndist() above Type string "RB" (2003), "UB$" (2007) Notes Returns the inverse of ndist() Uses a simple solver to return, as far as possible, the exact corresponding value and for this reason may be slower than some other functions Example Add-ins and Financial Applications. .. they can be combined many times with many 2 Note that this relies on code from Numerical Recipes in C omitted from the CD ROM Example Add-ins and Financial Applications 4 69 sets of normal samples in order to generate the correlated samples (See Clewlow and Strickland, Chapter 4.) In practice, therefore, the process needs to be broken down into the following steps: 1 2 3 4 5 6 Obtain or create the . expressions all do almost the same thing. (The third will return a #REF! error if A1¿3 and INDEX(C1:C3,1 .99 999 9761466)will round up to 2 and return C2, whereas 1 .99 999 9761465 will round down). Clearly,. of /) where integer arguments are being evaluated to an integer. • Iterate collections using For Each instead of For Next and indexing. • Iterate arrays using For Next and indexing instead of. case-insensitivity is needed. • Avoid late binding by declaring object variables as their explicit types instead of As Object . • Use integer division instead of floating point division ( instead