From: <aa...@us...> - 2016-03-05 12:28:07
|
Revision: 63096 http://sourceforge.net/p/firebird/code/63096 Author: aafemt Date: 2016-03-05 12:28:04 +0000 (Sat, 05 Mar 2016) Log Message: ----------- Optimized hash function for lock manager and hash join Modified Paths: -------------- firebird/trunk/builds/posix/make.rules firebird/trunk/builds/posix/prefix.linux firebird/trunk/builds/posix/prefix.linux_amd64 firebird/trunk/builds/win32/msvc10/common.vcxproj firebird/trunk/builds/win32/msvc10/common.vcxproj.filters firebird/trunk/builds/win32/msvc12/common.vcxproj firebird/trunk/builds/win32/msvc12/common.vcxproj.filters firebird/trunk/src/common/classes/Hash.h firebird/trunk/src/jrd/lck.cpp firebird/trunk/src/jrd/recsrc/HashJoin.cpp firebird/trunk/src/lock/lock.cpp firebird/trunk/src/lock/print.cpp Added Paths: ----------- firebird/trunk/src/common/CRC32C.cpp firebird/trunk/src/common/classes/Hash.cpp Modified: firebird/trunk/builds/posix/make.rules =================================================================== --- firebird/trunk/builds/posix/make.rules 2016-03-05 03:39:36 UTC (rev 63095) +++ firebird/trunk/builds/posix/make.rules 2016-03-05 12:28:04 UTC (rev 63096) @@ -32,16 +32,16 @@ # Please don't use compiler/platform specific flags here - nmcc 02-Nov-2002 -WFLAGS:=-I$(SRC_ROOT)/include/gen -I$(SRC_ROOT)/include $(CPPFLAGS) +WFLAGS =-I$(SRC_ROOT)/include/gen -I$(SRC_ROOT)/include $(CPPFLAGS) ifeq ($(TARGET),Release) - WFLAGS:= $(WFLAGS) $(PROD_FLAGS) + WFLAGS += $(PROD_FLAGS) else - WFLAGS:= $(WFLAGS) $(DEV_FLAGS) -DDEV_BUILD + WFLAGS += $(DEV_FLAGS) -DDEV_BUILD endif -WCFLAGS:= $(WFLAGS) $(THR_FLAGS) $(CFLAGS) $(GLOB_OPTIONS) -WCXXFLAGS:= $(WFLAGS) $(THR_FLAGS) $(RTTI_FLAG) $(CXXFLAGS) $(GLOB_OPTIONS) +WCFLAGS = $(WFLAGS) $(THR_FLAGS) $(CFLAGS) $(GLOB_OPTIONS) +WCXXFLAGS = $(WFLAGS) $(THR_FLAGS) $(RTTI_FLAG) $(CXXFLAGS) $(GLOB_OPTIONS) # Here we have definitions for using the preprocessor. Modified: firebird/trunk/builds/posix/prefix.linux =================================================================== --- firebird/trunk/builds/posix/prefix.linux 2016-03-05 03:39:36 UTC (rev 63095) +++ firebird/trunk/builds/posix/prefix.linux 2016-03-05 12:28:04 UTC (rev 63096) @@ -25,3 +25,6 @@ PROD_FLAGS=$(COMMON_FLAGS) $(OPTIMIZE_FLAGS) #DEV_FLAGS=-DUSE_VALGRIND -p $(COMMON_FLAGS) $(WARN_FLAGS) DEV_FLAGS=-p $(COMMON_FLAGS) $(WARN_FLAGS) + +# This file must be compiled with SSE4.2 support +%/CRC32C.o: COMMON_FLAGS += -msse4 Modified: firebird/trunk/builds/posix/prefix.linux_amd64 =================================================================== --- firebird/trunk/builds/posix/prefix.linux_amd64 2016-03-05 03:39:36 UTC (rev 63095) +++ firebird/trunk/builds/posix/prefix.linux_amd64 2016-03-05 12:28:04 UTC (rev 63096) @@ -25,3 +25,6 @@ PROD_FLAGS=$(COMMON_FLAGS) $(OPTIMIZE_FLAGS) #DEV_FLAGS=-DUSE_VALGRIND $(COMMON_FLAGS) $(WARN_FLAGS) DEV_FLAGS=$(COMMON_FLAGS) $(WARN_FLAGS) -fmax-errors=8 + +# This file must be compiled with SSE4.2 support +%/CRC32C.o: COMMON_FLAGS += -msse4 Modified: firebird/trunk/builds/win32/msvc10/common.vcxproj =================================================================== --- firebird/trunk/builds/win32/msvc10/common.vcxproj 2016-03-05 03:39:36 UTC (rev 63095) +++ firebird/trunk/builds/win32/msvc10/common.vcxproj 2016-03-05 12:28:04 UTC (rev 63096) @@ -30,6 +30,7 @@ <ClCompile Include="..\..\..\src\common\classes\ClumpletWriter.cpp" /> <ClCompile Include="..\..\..\src\common\classes\DbImplementation.cpp" /> <ClCompile Include="..\..\..\src\common\classes\fb_string.cpp" /> + <ClCompile Include="..\..\..\src\common\classes\Hash.cpp" /> <ClCompile Include="..\..\..\src\common\classes\ImplementHelper.cpp" /> <ClCompile Include="..\..\..\src\common\classes\init.cpp" /> <ClCompile Include="..\..\..\src\common\classes\InternalMessageBuffer.cpp" /> @@ -49,6 +50,16 @@ <ClCompile Include="..\..\..\src\common\config\ConfigCache.cpp" /> <ClCompile Include="..\..\..\src\common\config\config_file.cpp" /> <ClCompile Include="..\..\..\src\common\config\dir_list.cpp" /> + <ClCompile Include="..\..\..\src\common\CRC32C.cpp"> + <IntrinsicFunctions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</IntrinsicFunctions> + <IntrinsicFunctions Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</IntrinsicFunctions> + <IntrinsicFunctions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</IntrinsicFunctions> + <IntrinsicFunctions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</IntrinsicFunctions> + <DebugInformationFormat Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">ProgramDatabase</DebugInformationFormat> + <DebugInformationFormat Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">ProgramDatabase</DebugInformationFormat> + <DebugInformationFormat Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">ProgramDatabase</DebugInformationFormat> + <DebugInformationFormat Condition="'$(Configuration)|$(Platform)'=='Release|x64'">ProgramDatabase</DebugInformationFormat> + </ClCompile> <ClCompile Include="..\..\..\src\common\cvt.cpp" /> <ClCompile Include="..\..\..\src\common\db_alias.cpp" /> <ClCompile Include="..\..\..\src\common\dllinst.cpp" /> Modified: firebird/trunk/builds/win32/msvc10/common.vcxproj.filters =================================================================== --- firebird/trunk/builds/win32/msvc10/common.vcxproj.filters 2016-03-05 03:39:36 UTC (rev 63095) +++ firebird/trunk/builds/win32/msvc10/common.vcxproj.filters 2016-03-05 12:28:04 UTC (rev 63096) @@ -210,6 +210,12 @@ <ClCompile Include="..\..\..\src\common\Tokens.cpp"> <Filter>common</Filter> </ClCompile> + <ClCompile Include="..\..\..\src\common\classes\Hash.cpp"> + <Filter>classes</Filter> + </ClCompile> + <ClCompile Include="..\..\..\src\common\CRC32C.cpp"> + <Filter>classes</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="..\..\..\src\common\xdr_proto.h"> Modified: firebird/trunk/builds/win32/msvc12/common.vcxproj =================================================================== --- firebird/trunk/builds/win32/msvc12/common.vcxproj 2016-03-05 03:39:36 UTC (rev 63095) +++ firebird/trunk/builds/win32/msvc12/common.vcxproj 2016-03-05 12:28:04 UTC (rev 63096) @@ -30,6 +30,7 @@ <ClCompile Include="..\..\..\src\common\classes\ClumpletWriter.cpp" /> <ClCompile Include="..\..\..\src\common\classes\DbImplementation.cpp" /> <ClCompile Include="..\..\..\src\common\classes\fb_string.cpp" /> + <ClCompile Include="..\..\..\src\common\classes\Hash.cpp" /> <ClCompile Include="..\..\..\src\common\classes\ImplementHelper.cpp" /> <ClCompile Include="..\..\..\src\common\classes\init.cpp" /> <ClCompile Include="..\..\..\src\common\classes\InternalMessageBuffer.cpp" /> @@ -49,6 +50,12 @@ <ClCompile Include="..\..\..\src\common\config\ConfigCache.cpp" /> <ClCompile Include="..\..\..\src\common\config\config_file.cpp" /> <ClCompile Include="..\..\..\src\common\config\dir_list.cpp" /> + <ClCompile Include="..\..\..\src\common\CRC32C.cpp"> + <IntrinsicFunctions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</IntrinsicFunctions> + <EnableEnhancedInstructionSet Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">StreamingSIMDExtensions2</EnableEnhancedInstructionSet> + <IntrinsicFunctions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</IntrinsicFunctions> + <EnableEnhancedInstructionSet Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">StreamingSIMDExtensions2</EnableEnhancedInstructionSet> + </ClCompile> <ClCompile Include="..\..\..\src\common\cvt.cpp" /> <ClCompile Include="..\..\..\src\common\db_alias.cpp" /> <ClCompile Include="..\..\..\src\common\dllinst.cpp" /> Modified: firebird/trunk/builds/win32/msvc12/common.vcxproj.filters =================================================================== --- firebird/trunk/builds/win32/msvc12/common.vcxproj.filters 2016-03-05 03:39:36 UTC (rev 63095) +++ firebird/trunk/builds/win32/msvc12/common.vcxproj.filters 2016-03-05 12:28:04 UTC (rev 63096) @@ -210,6 +210,12 @@ <ClCompile Include="..\..\..\src\common\Tokens.cpp"> <Filter>common</Filter> </ClCompile> + <ClCompile Include="..\..\..\src\common\classes\Hash.cpp"> + <Filter>classes</Filter> + </ClCompile> + <ClCompile Include="..\..\..\src\common\CRC32C.cpp"> + <Filter>common</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="..\..\..\src\common\xdr_proto.h"> Added: firebird/trunk/src/common/CRC32C.cpp =================================================================== --- firebird/trunk/src/common/CRC32C.cpp (rev 0) +++ firebird/trunk/src/common/CRC32C.cpp 2016-03-05 12:28:04 UTC (rev 63096) @@ -0,0 +1,69 @@ +/* + * PROGRAM: Common Library + * MODULE: CRC32C.cpp + * DESCRIPTION: Hardware-accelerated hash calculation + * + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Dmitry Sibiryakov + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2015 Dmitry Sibiryakov + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + * + */ + +#include "firebird.h" + +// Can be used only on x86 architectures +// WARNING: With GCC must be compiled separately with -msse4.2 flag +#if defined(_M_IX86) || defined(_M_X64) || defined(__x86_64__) || defined(__i386__) + +#include <nmmintrin.h> +#include "../common/classes/Hash.h" + +namespace Firebird +{ +unsigned int CRC32C(const unsigned char* value, unsigned int length) +{ + unsigned int hash_value = 0; + if (length == 1) + { + return _mm_crc32_u8(hash_value, *value); + } + if (length == 2) + { + return _mm_crc32_u16(hash_value, *(unsigned short*)value); + } + while (length >= 4) + { + hash_value = _mm_crc32_u32(hash_value, *(unsigned int*)value); + value += 4; + length -= 4; + } + if (length >= 2) + { + hash_value = _mm_crc32_u16(hash_value, *(unsigned short*)value); + length -= 2; + } + if (length) + { + value += 2; + hash_value = _mm_crc32_u8(hash_value, *value); + } + return hash_value; +} +} // namespace +#endif // architecture check Property changes on: firebird/trunk/src/common/CRC32C.cpp ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: firebird/trunk/src/common/classes/Hash.cpp =================================================================== --- firebird/trunk/src/common/classes/Hash.cpp (rev 0) +++ firebird/trunk/src/common/classes/Hash.cpp 2016-03-05 12:28:04 UTC (rev 63096) @@ -0,0 +1,98 @@ +/* + * PROGRAM: Common Library + * MODULE: Hash.cpp + * DESCRIPTION: Hash of data + * + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Inprise Corporation + * and its predecessors. Portions created by Inprise Corporation are + * Copyright (C) Inprise Corporation. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + * + */ + +#include "firebird.h" +#include "../common/classes/Hash.h" + +namespace Firebird +{ + +static unsigned int basicHash(const unsigned char* value, unsigned int length) +{ + unsigned int hash_value = 0; + unsigned char* p; + const unsigned char* q = value; + while (length >= 4) + { + p = (unsigned char*) &hash_value; + p[0] += q[0]; + p[1] += q[1]; + p[2] += q[2]; + p[3] += q[3]; + length -= 4; + q += 4; + } + p = (unsigned char*) &hash_value; + if (length >= 2) + { + p[0] += q[0]; + p[1] += q[1]; + length -= 2; + } + if (length) + { + q += 2; + *p += *q; + } + return hash_value; +} + +#if defined(_M_IX86) || defined(_M_X64) || defined(__x86_64__) || defined(__i386__) + +#ifdef _MSC_VER + +#include <intrin.h> +#define bit_SSE4_2 (1 << 20) +// MS VC has its own definition of __cpuid +static bool SSE4_2Supported() +{ + int flags[4]; + __cpuid(flags, 1); + return (flags[2] & bit_SSE4_2) != 0; +} + +#else + +#include <cpuid.h> +// GCC - its own +static bool SSE4_2Supported() +{ + unsigned int eax,ebx,ecx,edx; + __cpuid(1, eax, ebx, ecx, edx); + return (ecx & bit_SSE4_2) != 0; +} + +#endif + +unsigned int CRC32C(const unsigned char* value, unsigned int length); + +someHashFunc someHash = SSE4_2Supported()?CRC32C:basicHash; +#else +someHashFunc someHash = basicHash; +#endif // Architecture check + +const char* hashName = someHash == CRC32C? "CRC32C": "Basic"; + +} // namespace \ No newline at end of file Property changes on: firebird/trunk/src/common/classes/Hash.cpp ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Modified: firebird/trunk/src/common/classes/Hash.h =================================================================== --- firebird/trunk/src/common/classes/Hash.h 2016-03-05 03:39:36 UTC (rev 63095) +++ firebird/trunk/src/common/classes/Hash.h 2016-03-05 12:28:04 UTC (rev 63096) @@ -331,6 +331,16 @@ }; // class iterator }; // class Hash +typedef unsigned int (*someHashFunc)(const unsigned char* value, unsigned int length); + +extern someHashFunc someHash; +extern const char* hashName; + +inline unsigned int hash(const void* value, unsigned int length, unsigned int hashSize) +{ + return someHash((const unsigned char*)value, length) % hashSize; +} + } // namespace Firebird #endif // CLASSES_HASH_H Modified: firebird/trunk/src/jrd/lck.cpp =================================================================== --- firebird/trunk/src/jrd/lck.cpp 2016-03-05 03:39:36 UTC (rev 63095) +++ firebird/trunk/src/jrd/lck.cpp 2016-03-05 12:28:04 UTC (rev 63096) @@ -28,6 +28,7 @@ #include "firebird.h" #include <stdio.h> +#include "../common/classes/Hash.h" #include "../jrd/jrd.h" #include "../jrd/lck.h" #include "gen/iberror.h" @@ -59,7 +60,6 @@ static bool compatible(const Lock*, const Lock*, USHORT); static void enqueue(thread_db*, CheckStatusWrapper*, Lock*, USHORT, SSHORT); static int external_ast(void*); -static USHORT hash_func(const UCHAR*, USHORT); static void hash_allocate(Lock*); static Lock* hash_get_lock(Lock*, USHORT*, Lock***); static void hash_insert_lock(Lock*); @@ -954,38 +954,6 @@ } - -static USHORT hash_func(const UCHAR* value, USHORT length) -{ -/************************************** - * - * h a s h - * - ************************************** - * - * Functional description - * Provide a repeatable hash value based - * on the passed key. - * - **************************************/ - - // Hash the value, preserving its distribution as much as possible - - ULONG hash_value = 0; - UCHAR* p = 0; - const UCHAR* q = value; - - for (USHORT l = 0; l < length; l++) - { - if (!(l & 3)) - p = (UCHAR*) &hash_value; - *p++ += *q++; - } - - return (USHORT) (hash_value % LOCK_HASH_SIZE); -} - - static void hash_allocate(Lock* lock) { /************************************** @@ -1036,7 +1004,7 @@ if (!att->att_compatibility_table) hash_allocate(lock); - const USHORT hash_value = hash_func((UCHAR*) &lock->lck_key, lock->lck_length); + const USHORT hash_value = hash(&lock->lck_key, lock->lck_length, LOCK_HASH_SIZE); if (hash_slot) *hash_slot = hash_value; Modified: firebird/trunk/src/jrd/recsrc/HashJoin.cpp =================================================================== --- firebird/trunk/src/jrd/recsrc/HashJoin.cpp 2016-03-05 03:39:36 UTC (rev 63095) +++ firebird/trunk/src/jrd/recsrc/HashJoin.cpp 2016-03-05 12:28:04 UTC (rev 63096) @@ -21,6 +21,7 @@ */ #include "firebird.h" +#include "../common/classes/Hash.h" #include "../jrd/jrd.h" #include "../jrd/btr.h" #include "../jrd/req.h" @@ -249,7 +250,7 @@ }; public: - HashTable(MemoryPool& pool, size_t streamCount, size_t tableSize = HASH_SIZE) + HashTable(MemoryPool& pool, size_t streamCount, unsigned int tableSize = HASH_SIZE) : PermanentStorage(pool), m_streamCount(streamCount), m_tableSize(tableSize), m_slot(0) { @@ -265,28 +266,11 @@ delete[] m_collisions; } - size_t hash(ULONG length, const UCHAR* buffer) const - { - ULONG hash_value = 0; - - UCHAR* p = NULL; - const UCHAR* q = buffer; - for (size_t l = 0; l < length; l++) - { - if (!(l & 3)) - p = (UCHAR*) &hash_value; - - *p++ += *q++; - } - - return (hash_value % m_tableSize); - } - void put(size_t stream, ULONG keyLength, const KeyBuffer* keyBuffer, ULONG offset, ULONG position) { - const size_t slot = hash(keyLength, keyBuffer->begin() + offset); + const unsigned int slot = hash(keyBuffer->begin() + offset, keyLength, m_tableSize); fb_assert(stream < m_streamCount); fb_assert(slot < m_tableSize); @@ -304,7 +288,7 @@ bool setup(ULONG length, const UCHAR* data) { - const size_t slot = hash(length, data); + const unsigned int slot = hash(data, length, m_tableSize); for (size_t i = 0; i < m_streamCount; i++) { @@ -350,7 +334,7 @@ private: const size_t m_streamCount; - const size_t m_tableSize; + const unsigned int m_tableSize; CollisionList** m_collisions; size_t m_slot; }; Modified: firebird/trunk/src/lock/lock.cpp =================================================================== --- firebird/trunk/src/lock/lock.cpp 2016-03-05 03:39:36 UTC (rev 63095) +++ firebird/trunk/src/lock/lock.cpp 2016-03-05 12:28:04 UTC (rev 63096) @@ -51,6 +51,7 @@ #include "../common/isc_s_proto.h" #include "../common/config/config.h" #include "../common/classes/array.h" +#include "../common/classes/Hash.h" #include "../common/classes/semaphore.h" #include "../common/classes/init.h" #include "../common/classes/timestamp.h" @@ -2087,7 +2088,6 @@ } #endif - lbl* LockManager::find_lock(USHORT series, const UCHAR* value, USHORT length, @@ -2107,23 +2107,9 @@ * **************************************/ - // Hash the value preserving its distribution as much as possible - - ULONG hash_value = 0; - { // scope - UCHAR* p = NULL; // silence uninitialized warning - const UCHAR* q = value; - for (USHORT l = 0; l < length; l++) - { - if (!(l & 3)) - p = (UCHAR*) &hash_value; - *p++ += *q++; - } - } // scope - // See if the lock already exists - const USHORT hash_slot = *slot = (USHORT) (hash_value % m_sharedMemory->getHeader()->lhb_hash_slots); + const USHORT hash_slot = *slot = (USHORT) Firebird::hash(value, length, m_sharedMemory->getHeader()->lhb_hash_slots); ASSERT_ACQUIRED; srq* const hash_header = &m_sharedMemory->getHeader()->lhb_hash[hash_slot]; Modified: firebird/trunk/src/lock/print.cpp =================================================================== --- firebird/trunk/src/lock/print.cpp 2016-03-05 03:39:36 UTC (rev 63095) +++ firebird/trunk/src/lock/print.cpp 2016-03-05 12:28:04 UTC (rev 63096) @@ -851,6 +851,7 @@ SLONG hash_max_count = 0; SLONG hash_min_count = 10000000; USHORT i = 0; + unsigned int distribution[21] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; for (const srq* slot = LOCK_header->lhb_hash; i < LOCK_header->lhb_hash_slots; slot++, i++) { SLONG hash_lock_count = 0; @@ -864,6 +865,9 @@ hash_min_count = hash_lock_count; if (hash_lock_count > hash_max_count) hash_max_count = hash_lock_count; + if (hash_lock_count > 20) + hash_lock_count = 20; + ++distribution[hash_lock_count]; } FPRINTF(outfile, "\tHash slots: %4d, ", LOCK_header->lhb_hash_slots); @@ -872,6 +876,16 @@ hash_min_count, (hash_total_count / LOCK_header->lhb_hash_slots), hash_max_count); + FPRINTF(outfile, "\tHash lengths distribution:\n"); + if (hash_max_count >= 20) + hash_max_count = 19; + for (int i = hash_min_count; i<=hash_max_count; ++i) + { + FPRINTF(outfile, "\t\t%-2d : %8u\t(%d%%)\n", i, distribution[i], distribution[i]*100/LOCK_header->lhb_hash_slots); + } + if (hash_max_count == 19) + FPRINTF(outfile, "\t\t> : %8u\t(%d%%)\n", distribution[20], distribution[20]*100/LOCK_header->lhb_hash_slots); + const shb* a_shb = (shb*) SRQ_ABS_PTR(LOCK_header->lhb_secondary); FPRINTF(outfile, "\tRemove node: %6"SLONGFORMAT", Insert queue: %6"SLONGFORMAT This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |