Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

622 lines
23 KiB

  1. <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
  2. <html>
  3. <head>
  4. <title>GeorgeRe's ToDo List for LKRhash</title>
  5. <link rel=Stylesheet type="text/css" media=all href="./docs/lkr.css">
  6. </head>
  7. <body>
  8. <h1>GeorgeRe's ToDo List for LKRhash</h1>
  9. <ul>
  10. <li> <a href="#testing"> Testing</a></li>
  11. <li> <a href="#kernel-mode"> Kernel Mode</a></li>
  12. <li> <a href="#lock-implementation"> Lock Implementation</a></li>
  13. <li> <a href="#iis6"> IIS 6.0</a></li>
  14. <li> <a href="#misc"> Miscellaneous</a></li>
  15. <li> <a href="#hashfn"> Hash Functions</a></li>
  16. <li> <a href="#docs"> Documentation</a></li>
  17. </ul>
  18. <p>These file is primarily for George Reilly's benefit, so don't
  19. worry if you don't understand all of the points.</p>
  20. <a name="testing">
  21. <h2>Testing</h2>
  22. </a>
  23. <ul>
  24. <li> Write a C test, to directly test the C API. HashTest half does
  25. it, as it can use the public API and TypedHashTable.
  26. <li> Build a publicly distributable test program, as a sample for
  27. customers. E.g., a dictionary component for ASP.
  28. <li> hashtest: modify so that each thread can work on a completely
  29. separate hashtable, instead of sharing a global
  30. hashtable. This will allow us to discover the maximum possible
  31. scaling, which is probably less than the `ideal' scaling,
  32. #CPUs * 1-thread-perf.
  33. <li> hashtest\kernel: split into driver and usermode test
  34. program. Usermode test program should take care of parsing the
  35. arguments and loading the test data into memory, then use an
  36. ioctl to pass it down to the kernel driver. The only code in
  37. the kernel driver,apart from LKRhash itself, should be the goo
  38. to crack the ioctl and DriverEntry. Usermode should print
  39. results.
  40. <li> Check that a numeric key of 0 actually works in debug
  41. version.
  42. <li> Better tests for ApplyIf family
  43. </ul>
  44. <a name="kernel-mode">
  45. <h2>Kernel Mode</h2>
  46. </a>
  47. <ul>
  48. <li> kernel locks: should they block at all? consider implications
  49. if running on some usermode program's thread, or of running at
  50. DISPATCH_LEVEL. Add support for using NtDelayExecution
  51. (implements Sleep) in SwitchOrSleep.
  52. <li> Fix global objects in driver, including the global list of
  53. tables; i.e., put in the necessary magic to ensure that
  54. constructors and destructors of globals objects get
  55. called.
  56. <li> implement kernel-mode version of !lkrhash
  57. <li> Write a template wrapper for operator new/delete, keyed
  58. on (Non)PagedPool.
  59. <li> Think about running at DISPATCH_LEVEL instead of
  60. PASSIVE_LEVEL. Which functions should be pageable
  61. vs. non-pageable? Memory allocators? Lock types?
  62. <li> Use <i>zwapi.h</i>? Which set of NT headers should we rely upon?
  63. <li> Memory allocation pool is a parameter for LKRhash public
  64. constructors (and LKR_Initialize), but it's ignored. Need
  65. virtual base for allocators? Note: m_paDirSegs allocates
  66. an array of CDirEntry*s.
  67. <li> DONE: Control debug spew: provide some way of setting
  68. g_fDebugOutputEnabled. (See flags to LKR_Initialize)
  69. </ul>
  70. <a name="lock-implementation">
  71. <h2>Lock Implementation</h2>
  72. </a>
  73. <ul>
  74. <li> More volatiles for Lock_AtomicCompareAndSwap calls.
  75. <li> Put delays into the inner loop of the spins, to see if that
  76. reduces cache line sloshing. See BLAM paper.
  77. <li> Use SleepEx(ms, TRUE) instead of Sleep(ms) to enable
  78. APCs. Does this leave any danger of deadlocking a COM STA
  79. thread?
  80. <li> Provide some way to expose the bit31 (precreate event)
  81. functionality of InitializeCriticalSectionAndSpinCount.
  82. Also, this function can fail.
  83. <li> Document that the locks are not suitable for
  84. cross-process use.
  85. <li> Enable per-class instrumentation for locks, as opposed to the
  86. current all-or-nothing system for per-lock instrumentation.
  87. Mostly present already, just needs a little factoring. Keep
  88. track of wall-clock time and sleep time too.
  89. <li> Check asserts for Is(Read|Write)Unlocked. Disable
  90. Is(Read|Write)(L|Unl)ocked except in debug builds. They're
  91. fundamentally broken. In general, you can't tell if this
  92. thread owns the lock for reading (sometimes, not even for
  93. writing). It's even harder to tell that you DON'T own the
  94. lock, especially when some other thread does.
  95. <li> Locks code. Move all member function implementations and enums
  96. into locks.cpp. Locks.h should be opaque declarations only.
  97. <li> Experiment with using bitfields or a union of WORDS and
  98. BYTEs, as opposed to the current explicit masking and
  99. shifting.
  100. <li> True waiter count in hiword? As opposed to waiters +
  101. writer, as currently.
  102. <li> Add TryConvertSharedToExclusive member to MRSW locks.
  103. <li> Look at the new EX_PUSH_LOCK in \nt\public\internal\base\inc\ntosp.h.
  104. Wrap in kLocks.h.
  105. <li> Is the current kernel mode GetCurrentThreadId adequate;
  106. i.e., is the Cid unique across all processes, including
  107. the System one?
  108. <li> Change Lock_AtomicIncrement to use `lock inc'. Ditto for
  109. Decrement. Don't need result of operation.
  110. <li> Remove x86 `lock' prefixes and measure difference on a 1P machine.
  111. <li> Experiment with putting the writer recursion count in
  112. m_lRW: 0xFFFF, 0xFFFE, ..., -n.
  113. <li> Rewrite a few key functions, such as CSmallSpinLock::WriteLock
  114. or CReaderWriterLock3::ReadOrWriteLock, in x86 assembler. Keep
  115. portable implementation, of course.
  116. <li> Add safe versions of the main entry points that do a lot
  117. more error checking: e.g., SafeWriteUnlock should check
  118. that the current thread actually owns the lock; check for
  119. over- or underflows; etc.
  120. <li> Orphaned lock detection?
  121. <li> Build locks as a separate statically linked library. Or dll?
  122. <li> Add per-class initialize and cleanup functions, to be called
  123. from LKR_Initialize
  124. <li> Make the locks throw exceptions (C++ or SEH?) in
  125. "impossible" conditions; e.g., recursively acquiring a
  126. CSmallSpinLock (=> instant deadlock), acquiring a
  127. writelock too many times, freeing a lock too many times.
  128. <li> Experiment with queued spinlocks: see \\bustard\contrib\ReneS\mcslock
  129. <li> Refcount Locks_Initialize and Locks_Cleanup?
  130. <li> Move CKSpinLock, CEResource, and CFastMutex into <Locks.h>.
  131. <li> Build another variant of the locks that waits on handles
  132. instead of using SwitchToThread. Perhaps a pool of handles?
  133. <li> Add a timeout feature to Try(Read|Write)Lock
  134. <li> Deadlock detection?
  135. <li> In the Spin routines, keep track of how long it's been
  136. since spinning started. DebugBreak if we've spun for a
  137. long time (e.g., 10 minutes)
  138. <li> For debugging purposes, keep track of all read owners of a
  139. multireader lock. Hang something out of TLS to keep track of
  140. all locks acquired by this thread.
  141. <li> InitializeCriticalSectionAndSpinCount is broken on
  142. Win98. Declared with wrong prototype or something. Only do
  143. a GetProcAddr for it on NT.
  144. <li> DONE: Put an InterlockedCompareExchange at the beginning of each
  145. outer loop in the spin routines, to ensure that there's an
  146. unconditional memory barrier.
  147. <li> DONE: Increase SL_OWNER_BITS from 4 to 8 to accommodate likely
  148. scenarios with locking iterators.
  149. <li> DONE: Sprinkle KeEnterCriticalRegion (and KeLeaveCriticalRegion)
  150. in the various locks, to prevent APCs being delivered that might
  151. suspend the thread that holds the lock.
  152. <li> NOT NEEDED, as I realized that the return value can be stored
  153. as a member variable that's written after the lock is acquired.
  154. WriteLock: make all return a value instead of `void' that's
  155. passed into WriteUnlock. Ditto for ReadLock, etc. Needed for
  156. CKSpinLock and OldIrql. <sigh/>
  157. </ul>
  158. <a name="iis6">
  159. <h2>IIS 6.0</h2>
  160. </a>
  161. <ul>
  162. <li> Reduce the three versions of LKRhash in IIS6 to just
  163. this one. Definitely get rid of the IISUTIL version, even
  164. if not allowed to fix the IISRTL/LISRTL version.
  165. <li> Remove all uses of the deprecated iterators from the
  166. IIS6 code base.
  167. <li> Port !lkrhash to ulkd. Update !cache or whatever it's called.
  168. <li> Copy the debug instrumentation from http.sys into CKSpinLock
  169. and CEResource.
  170. </ul>
  171. <a name="misc">
  172. <h2>Miscellaneous</h2>
  173. </a>
  174. <ul>
  175. <li> Don't __declspec(dllexport) CLKRLinearHashTable.
  176. Don't __declspec(dllexport) whole classes,
  177. just public methods.
  178. <li> Use ASSERTIONAL, PRECONDITION, etc, specialized macros.
  179. <li> Add typedefs for RecordPointer and HashSignature, to ease
  180. porting to AWE-type environment.
  181. <li> Run hashtest under the AppVerifier.
  182. <li> Compile with PREFast and /W4
  183. <li> Get rid of IsBad*Ptr wrappers in irtldbg.h. Clean up irtldbg.h and
  184. irtlmisc.h.
  185. <li> Experiment with __assume().
  186. <li> Get rid of all calls to Interlocked* in src\LKR-*.cpp.
  187. Replace with trivial methods. Have the methods use the lock type
  188. to decide whether to use interlocked or non-threadsafe operations.
  189. Goal is to have non-threadsafe version use no interlocked ops
  190. whatsoever.
  191. <li> Intel's Hyper-Threading presentation says to use a
  192. "Fill line size" of 128 bytes.
  193. <li> Provide BulkAdd and BulkDelete functions that work in
  194. terms of a collection of records. It should be cheaper to
  195. do _Expand (or _Contract) once for all affected buckets,
  196. than to have to do them after each insertion (deletion).
  197. <li> Allow a NULL AddRefRecord(), so that reference counting
  198. can be entirely dispensed with.
  199. <li> placement new for inline SubTable[0]
  200. <li> Clear() should abandon immediately if m_cRecords==0.
  201. <li> Replace use of NODES_PER_CLUMP in functions with a call
  202. to inline function _NodesPerClump(). This will allow
  203. experiments with NodeClumps whose size is set at runtime,
  204. not compile time. Also need to access m_dwKeySigs[i] and
  205. m_pvNode[i] through accessor functions.
  206. <li> :%s/CListEntry/LIST_ENTRY/g. This allows us to get rid
  207. of the global ctors/dtors (which are a pain in the kernel)
  208. and just initialize/test empty in LKR_Initialize and
  209. LKR_Terminate.
  210. <li> Use a custom allocator. CMediumSegment is 4KB on x86,
  211. but the heap overhead means that it's actually two pages,
  212. rather than exactly one page.
  213. <li> 'typedef INT_PTR NodeIndex' so that loop induction works
  214. better on Win64.
  215. <li> Experiment with disabling linear hashing's expansion and
  216. contraction. This would mean that we could avoid taking
  217. the table lock to calculate the bucket address. However,
  218. this would only work if the table lock is not exposed or
  219. explicitly disabled. It would also require the user to set
  220. the table size correctly in the constructor, if they don't
  221. use LK_{SMALL|MEDIUM|LARGE}_TABLESIZE.
  222. <li> In Win64/Debug version, InsertRecord should check for
  223. 32-bit overflow of m_cRecords. (Though it's still
  224. improbable. A record takes 8 bytes for the pointer + 4
  225. bytes for the hash signature plus some amortized overhead,
  226. which means that the storage for the table itself would be
  227. more than 12 * 4GB. And that excludes any storage for the
  228. &gt; 2^32 records.)
  229. <li> Add a "contention level" flag to the constructor. If the
  230. number of subtables is not specified explicitly (i.e.,
  231. LK_DFLT_NUM_SUBTBLS is passed to ctor), we key the number of
  232. subtables off LK_TABLESIZE multiplied by a function of the
  233. number of CPUs. However, we only need a lot of subtables under
  234. two circumstances: (a) many millions of elements (esp. on
  235. Win64, where total number of elements might someday exceed 2^32),
  236. or (b) high contention. There isn't necessarily a correlation
  237. between large table size and high contention. With the
  238. multi-reader locks, high contention only arises if there are a
  239. lot of insertions/deletions. If the table is not modified much
  240. after it's built, contention shouldn't be an issue and there's
  241. no real advantage to having a lot of subtables.
  242. <li> [Suggestion by BAlam] Provide a way to "seal" hashtable.
  243. Once the records have been inserted, call Seal(). All
  244. valid operations thereafter (except dtor) are readonly, so no
  245. table or bucket locks need be taken.
  246. <li> Provide a way to disable all locking. Caller takes
  247. responsibility for guaranteeing that all operations are
  248. threadsafe, either because table has thread affinity or
  249. because a higher-level lock is being used.
  250. <li> Add optional refcount parameter to template
  251. wrapper. Refcount all operations, hide dtor, and provide a
  252. Destroy method instead. Fix issues with some tables not
  253. shutting down gracefully.
  254. <li> Benchmark CLKRHashTable with one subtable vs. CLKRLinearHashTable.
  255. <li> Experiment with faster functions for finding subtable,
  256. esp. when number of subtables is not a power of 2.
  257. Probably just use middle 6 bits of ((hash &lt;&lt; 13) - hash)
  258. and use that to index into a lookup table of byte-sized
  259. remainders.
  260. <li> Use .w -&gt; .h stuff (hsplit) to filter out MS-confidential stuff
  261. from the headers, so that LKRhash.h and Locks.h can be
  262. rendered fit for public consumption?
  263. <li> Check that CLKRLinearHashTable can still be used as the
  264. base class for CTypedHashTable.
  265. <li> Add a default constructor to CTypedHashTable that uses a
  266. <code>static const char* ClassName()</code> method.
  267. <li> Should operator== and operator!= be made friend
  268. functions for the iterators, instead of members?
  269. <li> Add version.subversion number to CLKRLinearHashTable
  270. <li> Ensure all relevant fields are printed in !lkrhash
  271. <li> Add -k flag to !lkrhash to enumerate all known custom
  272. extensions.
  273. <li> The publicly exposed versions of the Lock accessors
  274. should use the Safe versions of the lock entrypoints. The
  275. internal versions (as called by the LKRhash code itself)
  276. can continue to use the fast versions.
  277. <li> Remove the deprecated iterators from the main code before
  278. releasing it. Bundle them in a separate subdirectory for
  279. the few holdouts. Clarify that they will not be released
  280. again.
  281. <li> Double-check <code>nCmp = pstrKey1-&gt;m_cch - pstrKey2-&gt;m_cch;</code>
  282. in WordHash.h
  283. <li> Use C#-style XML documentation comments
  284. <li> Write some documentation. Does there need to be a sanitized
  285. version for external consumption? Think about using XSL to
  286. achieve this.
  287. <li> Write a tutorial. Perhaps a little phone book class
  288. (email =&gt; phone number, then firstname+lastname =&gt;
  289. phone number). Extend it with a reverse map (phone number
  290. =&gt; name); mention canonicalization
  291. <li> See if division or multiplication is better in
  292. InsertRecord expansion test. Ditto for DeleteKey/Record.
  293. Experiment with using shifts for division. For example,
  294. NumRecords/7 ~= NumRecords * 9 / 64
  295. = (NumRecords &gt;&gt; 3) + (NumRecords &gt;&gt; 6).
  296. Don't need perfect mathematical accuracy.
  297. <li> Don't use modulo in CLKRHashTable::_SubTable, if m_cSubTables
  298. is not a power of 2. Either round the m_cSubTables up or down
  299. to the nearest power of 2, or compute a modulo table.
  300. <li> Add a context pointer to all of the *Key/Apply functions
  301. that will be passed down to CompareKeys and HashKey.
  302. E.g., do a case-sensitive or -insensitive match.
  303. <li> Make the m_pfn's be const pointers and member-initialize them.
  304. <li> Get rid of __LKRHASH_NO_NAMESPACE__; i.e., require the namespace.
  305. <li> Refcount LKR_Initialize and LKR_Terminate?
  306. <li> Add some flags to LKR_Initialize: default size, output
  307. tracing, etc
  308. <li> Should ApplyIf(LKP_DELETE) call the Action function before
  309. deleting? Or add LKP_PERFORM_DELETE[_STOP]
  310. <li> ApplyIf should not take the bucket lock. This would
  311. permit recursive calls to routines that need to take the
  312. bucket lock. Careful! There may be race conditions, if
  313. another thread holds an incompatible bucket lock but has
  314. been suspended.
  315. <li> Implement fMultiKeys to provide support for multiple,
  316. identical keys. Needed for EqualRange, hash_multiset, and
  317. hash_multimap. See CLKRLinearHashTable::_InsertRecord for
  318. details on what needs to be changed. Be sure to actually
  319. compare keys for equality: matching hash signatures is not
  320. enough.
  321. <li> Provide FindMulti, DeleteMulti(OUT Record*** pppRecords
  322. OPTIONAL), and FreeMulti.
  323. <li> Provide implementations of the STL collection classes:
  324. hash_map, hash_set, hash_multimap, and hash_multiset. Must
  325. provide full implementation of STL iterator semantics
  326. (pair&lt;key,value&gt;). Use the SGI version of hash_map
  327. as an inspiration, not the Dinkumware version. See
  328. Austern's book.
  329. <li> Provide const_iterators for STL-style iterators. But see
  330. what Scott Meyers has to say about this in "Effective STL."
  331. <li> Consider providing some kind of implicit locking with
  332. STL-style iterators. Either per-subtable or all subtables.
  333. <li> We need to call _ExtractKey a lot. Would it be better to
  334. cache the DWORD_PTR in each node? On the other hand,
  335. _ExtractKey is usually very cheap (typically add the
  336. offset of the key to the base address of the record), so
  337. the space-time tradeoff may not be worth it.
  338. <li> Add debug print routines for the other enumerations and for
  339. lock state to LKRhash. Place in a separate file that can be
  340. shared with lkrdbg.
  341. <li> Provide mapping functions, LKRetcodeToHResult,
  342. LKRetcodeToWin32Error, and LKRetcodeToNtStatus, for
  343. canonical translations.
  344. <li> Build lkrhash as a separate statically linked library
  345. <li> Make CBucket be a union with BYTE[BUCKET_BYTE_SIZE], to
  346. improve cache-line alignment. But really ought to be using
  347. an allocator that produces page-aligned locations for
  348. maximal benefit: i.e., something like RockAll instead of
  349. the NT heap or the CRT.
  350. <li> Better Statistics: #buckets, density, by subtable
  351. <li> Experiment with new hash function from Paul Larson and
  352. others.
  353. <li> Experiment with GUID hashing functions. Supposedly the
  354. first DWORD is enough. Rip all the IIDs, CLSIDs, etc out
  355. of the registry and create random GUIDs with UuidCreate()
  356. and MAC-based GUIDs with UuidCreateSequential() to test
  357. the distributions.
  358. <li> Public API in LKR-hash.h: remove dependency on irtlmisc.h
  359. and irtldbg.h
  360. <li> Coalesce CLKRLinearHashTable_Iterator members and
  361. CLKRHashTable_Iterator members for space; i.e., put
  362. `short m_iNode', `BYTE m_LockType', and `BYTE m_ist'
  363. together in CLKRLinearHashTable_Iterator.
  364. <li> Add some assertions in terms of MAX_DIRSIZE_BITS and
  365. NODE_CLUMP bits.
  366. <li> Use IsBadCodePtr to validate callback functions in
  367. constructor. Of course, this is no guarantee that they'll
  368. still be good functions later.
  369. <li> Make LKRhash exception-safe. What happens if callback routines
  370. (AddRefRecord/ExtractKeys or ApplyIf) access violate (throw an
  371. SEH exception) or throw a C++ exception? Table and bucket
  372. locks won't be released, state variables may be corrupted,
  373. etc. LKRhash code should never throw any kind of exception
  374. <li> Add throw() specifications to appropriate functions?
  375. <li> Add some kind of auto object for readlocking or writelocking a
  376. table, so that the table automatically gets unlocked by
  377. auto-obj's destructor.
  378. <li> Use auto_ptrs.
  379. <li> Port to C# (Chris Tracy has started on this).
  380. Andre Rosenthal has started a port to Managed C++
  381. <li> DONE: Still need NULL implementations for copy ctor and op=?
  382. Or is this an obsolete vestige of the long-gone templated
  383. inner classes?
  384. <li> DONE: Factor out memory allocation stuff into LKR-mem.cpp;
  385. initialization stuff into LKR-init.cpp; LKR-locks.cpp, etc.
  386. <li> DONE: Add TryWriteLock and TryReadLock methods to table. Also
  387. ConvertExclusiveToShared and vice versa.
  388. <li> DONE: Couch tests in _IsBucketChainMultiKeySorted et al in
  389. terms of a macro, CheckAndAdd.
  390. <li> DONE: Experiment with having no bucket locks whatsoever, just
  391. subtable locks. This should make operations a little
  392. faster, but presumably will hurt multiprocessor
  393. scalability. (Have to have table locks in multithreaded
  394. version as linear hashing's expansion/contraction means
  395. that bucket address calculation is a function of several
  396. variables that can change at any time.)
  397. <li> DONE: Add some instrumentation: #allocs, #expands, #contracts,
  398. #ExtractKeys, etc.
  399. <li> DONE: Reduce MIN_DIRSIZE to 4 and inline
  400. m_aDirSegs[4]. Only allocate if directory gets bigger than
  401. MIN_DIRSIZE.
  402. <li> DONE: Add an index member to subtable
  403. <li> DONE: Inline the array of pointers to subtables within
  404. CLKRHashTable, instead of dynamically allocating it.
  405. <li> DONE: Make AddRefRecord return the new reference
  406. count. Should it take a `const void*' or a `void*'?
  407. (TypedHashTable takes a non-const `Record*'.)
  408. <li> DONE: Provide a DeleteKey variant that returns a pointer to the
  409. record that is being removed from the table. In this case,
  410. instead of releasing reference, would implicitly transfer
  411. ownership of the record to the caller.
  412. <li> DONE: Inline _FindBucket by hand into Delete(Key|Record),
  413. InsertRecord, and Find(Key|Record).
  414. <li> DONE: Replace <code>IRTLVERIFY(x)</code> with
  415. <code>if (!x) IRTLASSERT(! "message")</code>.
  416. <li> DONE: InsertRecord should not LKAR_INSERT_RELEASE if record
  417. addresses are same. This guarantees that calls to
  418. _AddRefRecord with LKAR_MIN_DELETE_FROM_TABLE <= lkar <=
  419. LKAR_MAX_DELETE_FROM_TABLE works.
  420. <li> DONE: If m_cSubTables==1 don't bother to scramble the hash
  421. index in CLKRHashTable::_SubTable
  422. <li> DONE: break apart lkrhash.cpp: iterators, applyif, stats, etc
  423. <li> DONE: Always step forward through all subtables, to avoid
  424. possible deadlock, when acquiring subtable locks; i.e.,
  425. ensure that we have a valid lock hierarchy.
  426. <li> DONE: Test new contraction algorithm.
  427. <li> DONE: sublocks for ApplyIf
  428. <li> DONE: Provide a C API wrapper
  429. </ul>
  430. <a name="hashfn">
  431. <h2>Hash Functions</h2>
  432. </a>
  433. <ul>
  434. <li> Follow up on case-insensitive hash function for
  435. Unicode. Note that (wch &amp; 0xFFDF) is inadequate.
  436. Note: NTFS keeps a per-volume UpcaseMap for Unicode.
  437. <li> Provide A/W string hash functions that take a count,
  438. instead of relying on zero-termination.
  439. </ul>
  440. <a name="docs">
  441. <h2>Documentation</h2>
  442. </a>
  443. <ul>
  444. <li> Write a tutorial
  445. </ul>
  446. </ul>
  447. <hr>
  448. <address></address>
  449. <!-- hhmts start -->
  450. Last modified:
  451. Fri Feb 15 13:15:54 2002
  452. <!-- hhmts end -->
  453. </body>
  454. </html>