Generally, are the programs written by C++ slower than written by C10% ?

B

BGB

Rightly so. C has things call functions that enable code to be reused
and C++ has function templates that remove the last excuse for copy and
paste.


Not if they were being paid to deliver software.

well, if they just copy/paste it from existing sources, they may be
stuck with GPL nastiness or similar (since, annoyingly, the majority of
existing/FOSS source is GPL), so writing it initially will give them a
version for which they control the license (while still being legal, and
not simply "forgetting" the per-existing license terms).


for example, I have ended up having to write pretty much a whole damn 3D
engine to get away from the GPL and stay reasonably close to familiar
territory (my plans are to use the MIT license for most of the core).

recently I have (at least experimentally) loaded up Doom3 maps in my own
engine, however for the most part I have previously been using
Quake-style maps (mostly from Quake 1). I had also loaded up maps from
Xonotic (sort of like a Quake-3 Arena clone based on a heavily modified
Quake-1 engine, namely DarkPlaces).

Most C++ code passes strings by const reference.

I guess people do both.

I had seen references to it apparently being a style debate between
using references and passing them by value, with different performance
tradeoffs existing between compilers.

With some compilers in some modes, passing a string by value will be
quicker if the string object (typically a pair of pointers) is small
enough to pass in registers.

dunno, it depends on the compiler.

the version of the object I am familiar with happens to contain (IIRC) a
reference count, a maximum size (capacity), a length field, and a
character array aliased against a pointer.

In other words, wrapping them like std::string...

yep, except that this also works with plain C (relevant since the
project is largely mixed-language, and C-style is the only thing
"everyone" understands).


actually, it is more restricted than this, typically not only do they
have to be plain C types but also visible to the garbage collector and
to the VM type-system.

technically, the pointer values are equivalent, but different functions
are used to return either "char *" or "dyt" (a magic type used for
dynamically typed objects), mostly to avoid the need for manual casting
or the compiler throwing a fit due to implicit pointer-type conversions.


trivia:
there are overloaded operators so that "dyt" behaves more like a
built-in type in C++, whereas one needs things like "dyadd(a, b)" in C.

for many shared-language constructs, it is usually least effort to make
them behave C-like internally, and give optional overloaded operators or
wrapper classes for the C++ case.

in a few cases, the methods actually just redirect, either to calling
through a function-pointer (for C and C++ code to implement the real
logic), or redirecting the call into the scripting VM (such as if a
VM-managed object is placed into a field, such as "dy_this").

say, for example:
void FooObject::fooMethod()
{
if(dy_this)
{
dyCallSigv(dy_this, "fooMethod", "()v");
return;
}
if(vtable->fooMethof_f)
vtable->fooMethof_f();
}

it can all get kind of hairy at times, but it works...

Or save a whole lot of pain and use std::string.

except, as noted above, I can't do this generally, as C++ is only used
in parts of the project.

granted, std::string makes a lot more sense for a pure-C++ project (or
at least a "strongly dominantly C++" project).
 
J

Juha Nieminen

BGB said:
in terms of usability, maybe.
there is more to the world than usability though.

I don't understand. Your argument seems to be: "There are other things
besides usability that a programmer has to take into account. Hence
usability is not important and can be dropped." That argument makes no
sense to me.

It sounds like: "There are other things besides the driving wheel that
need to be used in order to drive a car. Hence we can drop the driving
wheel completely." Makes no sense.
 
N

Nick Keighley

different styles for different sorts...

why? Why not use something that is good-enough? Scraping an extra nano-
second off an infrequently run routine is hardly ever worth the
bother.

actually, in my case I would probably far more likely copy-paste it and
then edit it to the new use.

copy-paste is a powerful tool which is often disparaged and overlooked.

copy-paste is the route of all evil. I've suggested (only half joking)
that the single best way to improve the quality of software world-wide
is to disable the copy-paste facility. Or slightly hurt the programmer
every time he uses it.
but, one would typically write it themselves initially (before
subjecting it to endless copy-paste).

gag. Why not package it a proper reusable component? (function,
template, macro, package). Then just reuse it. You're trying to drag
us back to the software engineering of the neolithic.
if people were not doing it, then one would not be encountering them.

so tell them to stop! Don't encourage them!

that one is encountering so many "badly written sort functions" means
that there are equally many (probably actually, far more) people off
writing them.

hence, the case stands...

hence use the bloody library routine

there are also plenty of things often done with C strings as well that
can make them faster, such as interning them so that one can check
string equality using '==' on the pointers, rather than using
"!strcmp()", ...

in my case, I tend to avoid "stdrup()" as well, as generally "strdup()"
is a good way to "make bad things happen"

which is probably why strdup() is non-standard

(slow, tears through huge
amounts of memory, leaves the program responsible to free them, ...), [...]

<snip>
 
M

Miles Bader

BGB said:
in terms of usability, maybe.
there is more to the world than usability though.

Sure... but C++ is more usable, tends to result in faster (in
execution) programs, yields more maintainable, easier to read code,
and makes it much easier to write more robust programs, more quickly,
than C...

The downside is that it is more complex ("more rope") and so requires
somewhat more learning (tho not nearly as much as many seem to think
-- much of the complexity in C++ doesn't really manifest unless you're
doing advanced things).

Not a bad set of tradeoffs to be honest....

-Miles
 
N

Nick Keighley

well, if they just copy/paste it from existing sources, they may be
stuck with GPL nastiness or similar (since, annoyingly, the majority of
existing/FOSS source is GPL), so writing it initially will give them a
version for which they control the license (while still being legal, and
not simply "forgetting" the per-existing license terms).

I don't get this. How does copy-paste save you from licence problems?
If you've incorporated GPL code into your code base then surely any
GPL restrictions that apply already apply! I think the people who
think they're avoiding GPL problems[1] by copy-pasteing are fooling
themselves.

[note 1] I'm not going to get into a debate about the meaning of the
word "problem"
for example, I have ended up having to write pretty much a whole damn 3D
engine to get away from the GPL and stay reasonably close to familiar
territory (my plans are to use the MIT license for most of the core).

and what has that got to do with copy-paste?

<snip>
 
K

KaiWen

Rightly so.  C has things call functions that enable code to be reused
and C++ has function templates that remove the last excuse for copy and
paste.


Not if they were being paid to deliver software.



Most C++ code passes strings by const reference.




With some compilers in some modes, passing a string by value will be
quicker if the string object (typically a pair of pointers) is small
enough to pass in registers.


In other words, wrapping them like std::string...





Or save a whole lot of pain and use std::string.


My test code:

#include <iostream>
#include <string>
#include <cstring>

int main() {
clock_t tbeg;

{
tbeg = clock();
for (int i = 0; i < 10000000; i++)
std::string("hello, world!");

std::cout << "test 1 use " << clock() - tbeg <<
std::endl;
}

{
tbeg = clock();
for (int i = 0; i < 10000000; i++) {
char* str = new char[20];
strcpy(str, "hello, world!");
delete [] str;
}

std::cout << "test 2 use " << clock() - tbeg <<
std::endl;
}

{
tbeg = clock();
for (int i = 0; i < 10000000; i++) {
char* str = (char*)malloc(20);
strcpy(str, "hello, world!");
free(str);
}

std::cout << "test 3 use " << clock() - tbeg <<
std::endl;
}

{
tbeg = clock();
for (int i = 0; i < 10000000; i++) {
char str[20];
strcpy(str, "hello, world!");
}

std::cout << "test 4 use " << clock() - tbeg <<
std::endl;
}

return 0;
}

output:
----------------------------
test 1 use 1260000
test 2 use 850000
test 3 use 610000
test 4 use 20000

So, could I think that if an object is constructed and destructed
frequently, I should make its member data are POD (not std::string or
some other expensive thing) ? Like this:

class temp_cache1 {
private:
std::string s;
std::vector<int> v;
};

class temp_cache2 {
private:
char s[SIZE];
int v[SIZE];
};

Here, is temp_cache2 better than temp_cache1?

I use valgrind trace the test program, found that the std::string
will allocation memory from heap even if the string is very short,
like std::string("abc"), it will allocation memory from heap.

So, either use temp_cache2 or use temp_cache1 and don't let it
destructed,
reuse its s and v. Am I right?
 
I

Ian Collins

My test code:

#include<iostream>
#include<string>
#include<cstring>

int main() {
clock_t tbeg;

{
tbeg = clock();
for (int i = 0; i< 10000000; i++)
std::string("hello, world!");

std::cout<< "test 1 use "<< clock() - tbeg<<
std::endl;
}

{
tbeg = clock();
for (int i = 0; i< 10000000; i++) {
char* str = new char[20];
strcpy(str, "hello, world!");
delete [] str;
}

std::cout<< "test 2 use "<< clock() - tbeg<<
std::endl;
}

{
tbeg = clock();
for (int i = 0; i< 10000000; i++) {
char* str = (char*)malloc(20);
strcpy(str, "hello, world!");
free(str);
}

std::cout<< "test 3 use "<< clock() - tbeg<<
std::endl;
}

{
tbeg = clock();
for (int i = 0; i< 10000000; i++) {
char str[20];
strcpy(str, "hello, world!");
}

std::cout<< "test 4 use "<< clock() - tbeg<<
std::endl;
}

return 0;
}

output:
----------------------------
test 1 use 1260000
test 2 use 850000
test 3 use 610000
test 4 use 20000

So, could I think that if an object is constructed and destructed
frequently, I should make its member data are POD (not std::string or
some other expensive thing) ? Like this:

Why do you think std::string is necessary expensive? When I tried you
code, I got

test 1 use 650000
test 2 use 660000
test 3 use 570000
test 4 use 0
class temp_cache1 {
private:
std::string s;
std::vector<int> v;
};

class temp_cache2 {
private:
char s[SIZE];
int v[SIZE];
};

Here, is temp_cache2 better than temp_cache1?

I use valgrind trace the test program, found that the std::string
will allocation memory from heap even if the string is very short,
like std::string("abc"), it will allocation memory from heap.

So, either use temp_cache2 or use temp_cache1 and don't let it
destructed,
reuse its s and v. Am I right?

If you want to avoid heap allocations and know your size requirements up
front, then obviously a static solution will be faster. Until you
actually want to manipulate the strings....
 
K

KaiWen

Why do you think std::string is necessary expensive? When I tried you
code, I got

test 1 use 650000
test 2 use 660000
test 3 use 570000
test 4 use 0

I test the code in a virtual machine, Fedora 15 witch in the Virtual
Box.
756M memory, 2G HZ.

It is slower than malloc, it's true. Unless reuse it.
 
A

Asger-P

Hi Ian

Why do you think std::string is necessary expensive? When I tried you
code, I got

test 1 use 650000
test 2 use 660000
test 3 use 570000
test 4 use 0

On windows using CB2009

test 1 use 589
test 2 use 752
test 3 use 526
test 4 use 85

No penalty here for using std::string either, I dont know why
my numbers are so much smaller though, mine is in milliseconds,
I use a WinAPI function caled timeGetTime() i couldn't find Clock().


Best regards
Asger-P
 
N

none

this again, comes down to coding practices...


also, just how many people really use "qsort()" anyways?...
IME, it is more often one of those largely forgotten functions (it is
there, but more often people write out their own sort logic manually).

(actually, it seems to be fairly common practice in C land for people to
largely ignore much of the standard library, and to write their own
logic for doing things).

Which has two effects:

1- Waste programmer time. Time that could otherwised be used to
improve the rest of the program.

2- One of:
a) Best case: an equivalently well implementation as a quality std
library
b) A working but somewhat poorer implementation that would would
have been produced but a good dedicated team and tested
extensively.
c) a buggy implementation that comes back to bite you.

So even in the best case scenario, you are loosing if there is not a
quality standard library available.

One of the problem with C is that the basic C library offers very
little and nobody agrees on what is a quality generally accepted
extension to it. So peoples spend time reimplementing the wheel or
re-learning a different third party library.

Yan
 
B

BGB

Sure... but C++ is more usable, tends to result in faster (in
execution) programs, yields more maintainable, easier to read code,
and makes it much easier to write more robust programs, more quickly,
than C...

The downside is that it is more complex ("more rope") and so requires
somewhat more learning (tho not nearly as much as many seem to think
-- much of the complexity in C++ doesn't really manifest unless you're
doing advanced things).

Not a bad set of tradeoffs to be honest....

but, it is harder to write parsers and custom analysis tools for, and
has a more complex and less standardized ABI.

hence, it is more difficult to implement things like reflection, ... for
it (very useful for automated cross language interfacing).

writing a C++ compiler is likely also to be a non-trivial task (vs a C
compiler, which is already a bit painful).

....
 
B

BGB

I don't understand. Your argument seems to be: "There are other things
besides usability that a programmer has to take into account. Hence
usability is not important and can be dropped." That argument makes no
sense to me.

It sounds like: "There are other things besides the driving wheel that
need to be used in order to drive a car. Hence we can drop the driving
wheel completely." Makes no sense.

as noted a number of times:
I do use C++;
I just don't use it everywhere (and the places where I don't use it may
also influence what is done in the places where I do use it).

it can't be used in places where I need to be able to parse the code by
tools (or be able to run the code through my own compiler, ...);
it can't be used in places where I need to have the functionality shared
with my scripting language;
....

being able to automatically-interface the code between languages, ... is
a lot more valuable than using a slightly more programmer-friendly
language, but then having to write a mountain of boilerplate because it
is no longer possible to auto-generate much of the cross-language glue.

....
 
B

BGB

well, if they just copy/paste it from existing sources, they may be
stuck with GPL nastiness or similar (since, annoyingly, the majority of
existing/FOSS source is GPL), so writing it initially will give them a
version for which they control the license (while still being legal, and
not simply "forgetting" the per-existing license terms).

I don't get this. How does copy-paste save you from licence problems?
If you've incorporated GPL code into your code base then surely any
GPL restrictions that apply already apply! I think the people who
think they're avoiding GPL problems[1] by copy-pasteing are fooling
themselves.

no, this is why they write the code themselves:
if they write it themselves, they are free of GPL.

if they just go and either use GPL code, or copy-paste from GPL sources,
then they are stuck with GPL.

hence, why one needs to write their own code themselves (then they can
use it however without any legal restriction).

[note 1] I'm not going to get into a debate about the meaning of the
word "problem"
for example, I have ended up having to write pretty much a whole damn 3D
engine to get away from the GPL and stay reasonably close to familiar
territory (my plans are to use the MIT license for most of the core).

and what has that got to do with copy-paste?

one of the major "world players" in the FOSS 3D engine space is various
modified versions of the Quake-series engines ("Quake engine" in the
generic sense).

however, *all* of these engine variants are GPL, because id Software
originally released their source under the GPL.


hence, why one would need to write their own engine from the ground up
to be free of the GPL.

one can't just "copy-paste" the 3D engine, or parts of it, without being
stuck with the GPL.


now, extend this generally, and note that most freely-available code is
either proprietary (can't be reused safely at all) or GPL, and hence one
starts to see the problem, namely: why it is necessary for people to
write their own code to keep everything legal.

yes, there are MIT / BSD / ... projects, which allow more freely reusing
code without overbearing legal issues, but these projects are a relative
minority in FOSS land.
 
B

BGB

Which has two effects:

1- Waste programmer time. Time that could otherwised be used to
improve the rest of the program.

yes, but doesn't matter if other people are wasting their time, only if
they are wasting ones' own time (or oneself is wasting ones' own time
and has potentially better things to be doing).

given enough people, and a lack of profit motive, then the cost/value of
most effort ultimately tends towards zero (actually, it is RMS's ideal
to impose this on all software, but this is not agreeable IMO, as
commercial people doing their thing still manage to deliver a lot more
that is usable to "typical end users", and to produce higher-quality
games with better production values, ...).

it is like, at the moment the high-point of production values in FOSS
games is still only really up to what commercial games had 10-12 years
ago (sadly, we don't see any Portal or Portal 2 analogues or similar
coming from FOSS land at present...).


granted, GIMP and FireFox are pretty good (and Internet Explorer is
still generally "teh suck"...).

2- One of:
a) Best case: an equivalently well implementation as a quality std
library
b) A working but somewhat poorer implementation that would would
have been produced but a good dedicated team and tested
extensively.
c) a buggy implementation that comes back to bite you.

So even in the best case scenario, you are loosing if there is not a
quality standard library available.

One of the problem with C is that the basic C library offers very
little and nobody agrees on what is a quality generally accepted
extension to it. So peoples spend time reimplementing the wheel or
re-learning a different third party library.

fair enough.

this is probably why, for the most part, projects tend to end up
creating their own library "platform" and building their code on this,
rather than using much of the standard C library.

but, then one ends up with issues like largish apps (500 kloc or 1 Mloc
or more) built on things like GTK+, including many components with
nothing to do with GUI, which has its own drawbacks.

better options may well just be someone writing their own set of
run-time libraries (this is basically the main area where my "C
restriction" is in effect, application code does not have this
restriction, and libraries may be C++ if they are more "internal logic"
than "external API").

another option is to use a less-nasty framework (than GTK+), such as SDL
or similar.

granted, the above doesn't apply to large pre-existing apps, which may
already be heavily built around GTK+ or whatever.


in some ways, writing software is often not too much different from
throwing on vasaline in place of thermal grease (say, because one
doesn't have any thermal grease). it is maybe not as good, or maybe will
all leak away after a while, but it is a good enough temporary fix until
next time the issue comes up again (where one does have the grease, or
worse case, they throw on a new coat of vasaline...).

actually, it works fairly good for things like electric motors, and
apparently for 4-cycle engines (like in cars/...) as well.

(but, I am more wary of crisco, as things have turned out badly before
with this one... just because it works good in ovens apparently doesn't
make it good for electronics even if both use-cases have to deal with
heat...).

it is not just vegetable shortening, but maybe also electrical shortening...


or such...
 
A

Asger-P

Hi Paavo

Modified the test a little to make sure the std::string
actually was created, it makes quite a difference:

#include <iostream>
#include <string>
#include <cstring>
#include <time.h>
#include <stdlib.h>
#include <conio.h>

int _tmain(int argc, char* argv[])
{
clock_t tbeg;
char *cStr = "123456789012345678901234567890";
const int L = strlen( cStr ) + 1;


tbeg = clock();
for (int i = 0; i < 10000000; i++)
{
std::string test(cStr);
if( test[5] == '0' )
std::cout << "error" << std::endl;
}

std::cout << "test 1 use " << clock() - tbeg << std::endl;


tbeg = clock();
for (int i = 0; i < 10000000; i++)
{
char* str = new char[L];
strcpy(str, cStr);
if( str[5] == '0' )
std::cout << "error" << std::endl;
delete [] str;
}

std::cout << "test 2 use " << clock() - tbeg << std::endl;


tbeg = clock();
for (int i = 0; i < 10000000; i++)
{
char* str = (char*)malloc(L);
strcpy(str, cStr);
if( str[5] == '0' )
std::cout << "error" << std::endl;
free(str);
}

std::cout << "test 3 use " << clock() - tbeg << std::endl;


getch();
return 0;
}

test 1 use 1451
test 2 use 998
test 3 use 749

why is malloc faster then new ??

Best regards
Asger-P
 
A

Asger-P

Hi Paavo Helde

std::string test(cStr, L-1);

And yes, passing L here makes the tests more fair, as malloc/new got the
size L, but std::string did not in the original version.

That is right, but then the std::string knows the length all
the way arround, so I think it would e fair to give the same
advantage to char array, like this.

int main(int argc, char* argv[])
{
clock_t tbeg;
char *cStr =
"123456789012345678901234567890123456789012345678901234567890";
const int L = strlen( cStr );


tbeg = clock();
for (int i = 0; i < 10000000; i++)
{
std::string test(cStr, L);
std::string test2 = test;
test2 += test;
if( test2[5] == '0' )
std::cout << "error" << std::endl;
}

std::cout << "test 1 use " << clock() - tbeg << std::endl;


tbeg = clock();
for (int i = 0; i < 10000000; i++)
{
char* str = new char[L+1];
strncpy(str, cStr, L);
char* str2 = new char[L*2 + 2];
strncpy(str2, str, L);
strncpy(&str2[L], str, L);
if( str2[5] == '0' )
std::cout << "error" << std::endl;
delete [] str;
delete [] str2;
}

std::cout << "test 2 use " << clock() - tbeg << std::endl;


tbeg = clock();
for (int i = 0; i < 10000000; i++)
{
char* str = (char*)malloc(L+1);
strncpy(str, cStr, L);
char* str2 = (char*)malloc(L*2 + 2);
strncpy(str2, str, L);
strncpy(&str2[L], str, L);
if( str2[5] == '0' )
std::cout << "error" << std::endl;
free(str);
free(str2);
}

std::cout << "test 3 use " << clock() - tbeg << std::endl;


getch();
return 0;
}

and then the numbers are really in favor of char array,
not the amount of coding though.

test 1 use 3744
test 2 use 2090
test 3 use 1623

Best regards
Asger-P
 
A

Asger-P

Hi Paavo

#include <conio.h>

Not a standard header
int _tmain(int argc, char* argv[])

Not a standard signature ....snip...

Not a standard function

It's just me never doing any commandline apps. ;-)
Do You really care about that :O
I get:
test 1 use 688
test 2 use 620
test 3 use 586

Are you sure you are using an optimized build?

Yes, I do a release build in my CB2009, so that is odd,
but then again I usually use the VCL UnicodeString, I
never tested the std::string before, so I might need to
do some other tweaking.
new typically forwards to malloc behind the scenes, so it is a
malloc+something extra (at least one function call more). As this test
code does not do much more (strcpy() gets inlined by my compiler), I .....snip....
practical purposes, except of showing that the performance of all these
approaches is comparable.

Thanks You very much for explaining.

Best regards
Asger-P
 
N

Noah Roberts

Hi Paavo Helde

           std::string test(cStr, L-1);
And yes, passing L here makes the tests more fair, as malloc/new got the
size L, but std::string did not in the original version.

That is right, but then the std::string knows the length all
the way arround, so I think it would e fair to give the same
advantage to char array, like this.

int main(int argc, char* argv[])
{
    clock_t tbeg;
    char *cStr =  
"123456789012345678901234567890123456789012345678901234567890";
    const int L = strlen( cStr );

    tbeg = clock();
    for (int i = 0; i < 10000000; i++)
    {
       //std::string test(cStr, L);
       //std::string test2 = test;
       //test2 += test;
std::string test = cStr;
std::string test2;
test2.reserve(L*2);
test2.append(test).append(test);
       if( test2[5] == '0' )
          std::cout << "error" << std::endl;
    }

    std::cout << "test 1 use " << clock() - tbeg << std::endl;

    tbeg = clock();
    for (int i = 0; i < 10000000; i++)
    {
       char* str = new char[L+1];
       strncpy(str, cStr, L);
       char* str2 = new char[L*2 + 2];
       strncpy(str2, str, L);
       strncpy(&str2[L], str, L);
       if( str2[5] == '0' )
          std::cout << "error" << std::endl;
       delete [] str;
       delete [] str2;
    }

    std::cout << "test 2 use " << clock() - tbeg << std::endl;

    tbeg = clock();
    for (int i = 0; i < 10000000; i++)
    {
       char* str = (char*)malloc(L+1);
       strncpy(str, cStr, L);
       char* str2 = (char*)malloc(L*2 + 2);
       strncpy(str2, str, L);
       strncpy(&str2[L], str, L);
       if( str2[5] == '0' )
          std::cout << "error" << std::endl;
       free(str);
       free(str2);
    }

    std::cout << "test 3 use " << clock() - tbeg << std::endl;

    getch();
    return 0;

}

and then the numbers are really in favor of char array,
not the amount of coding though.

test 1 use 3744

Fixed it for you.
 
M

Miles Bader

BGB said:
but, it is harder to write parsers and custom analysis tools for, and
has a more complex and less standardized ABI.

hence, it is more difficult to implement things like reflection,
... for it (very useful for automated cross language interfacing).

writing a C++ compiler is likely also to be a non-trivial task (vs a C
compiler, which is already a bit painful).

Those are true (I know this because I have done a lot of C++ compiler
work), but frankly, people writing C++ parsers / compilers / etc, are
a miniscule fraction of the people using C++ for other tasks.

So what you have, basically, is harder work for a few, to make things
easier/better/safer for many.

Again, a pretty decent tradeoff.

[and given that I'm part of "the few", as well as part of the "many"
(I use C++ for many tasks), I think I have a pretty reasonable view of
this tradeoff. Indeed, for compiler writers, the additional
complexity isn't some sort of pure annoyance -- it's also in many ways
more interesting to work on (and for those writing their compiler in
C++, of course, a benefit)... :]

-Miles
 
A

Asger-P

Hi Paavo

It means other people can not just copy-paste your code and compile-run
it by themselves.

Well then I better ask how You get the console window to stay open
long enough to read the result, when doing a build and run.?
if You dont use something like getch();


Best regards
Asger-P
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top