mirror of
https://github.com/berkeleydb/libdb.git
synced 2024-11-16 17:16:25 +00:00
640 lines
15 KiB
C
640 lines
15 KiB
C
/*
|
|
* See the file LICENSE for redistribution information.
|
|
*
|
|
* Copyright (c) 2011 Oracle and/or its affiliates. All rights reserved.
|
|
*
|
|
* $Id$
|
|
*
|
|
* This program demonstrates:
|
|
* 1. Usage of heap access method.
|
|
* 2. Differences between the heap and btree access methods.
|
|
*
|
|
* The application initially populates a database, and then proceeds to
|
|
* move into a process of adding and removing data. Keeping a fairly
|
|
* constant amount of data in the database. The heap access method will
|
|
* maintain a constant database size if the heap size is configured properly,
|
|
* while the btree database will continue to grow.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "db.h"
|
|
|
|
#ifndef lint
|
|
static const char copyright[] =
|
|
"Copyright (c) 2011 Oracle and/or its affiliates. All rights reserved.\n";
|
|
#endif
|
|
|
|
#define BUFFER_LEN 30 /* Buffer size to hold data */
|
|
#define NS_PER_MS 1000000/* Nanoseconds in a millisecond*/
|
|
#define NS_PER_US 1000 /* Nanoseconds in a microsecond*/
|
|
#define DEF_INIT_RECS 10000 /* Default initial records */
|
|
#define DEF_RECS_PER_REP 10000 /* Default records per repeat. */
|
|
#define DEF_REPEATS 1 /* Default repetition value */
|
|
|
|
/*
|
|
* Average space each record needs, based on the data generated.
|
|
* The ideal heap size for this example should be set as (bytes):
|
|
* AVG_SPACE_PER_RECORD * (DEF_INIT_RECS + records inserted each repetition)
|
|
*/
|
|
#define AVG_SPACE_PER_RECORD 36
|
|
|
|
#ifdef _WIN32
|
|
#include <sys/timeb.h>
|
|
#include <time.h>
|
|
|
|
extern int getopt(int, char * const *, const char *);
|
|
|
|
/* Implement a basic high resource timer with a POSIX interface for Windows.*/
|
|
struct timeval {
|
|
time_t tv_sec;
|
|
long tv_usec;
|
|
};
|
|
|
|
int gettimeofday(struct timeval *tv, struct timezone *tz)
|
|
{
|
|
struct _timeb now;
|
|
_ftime(&now);
|
|
tv->tv_sec = now.time;
|
|
tv->tv_usec = now.millitm * NS_PER_US;
|
|
return (0);
|
|
}
|
|
#else
|
|
#include <sys/time.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
int compare_int(DB *, const DBT *, const DBT *);
|
|
int delete_recs __P((DB *, DB_ENV *, int));
|
|
int file_size __P((DB *, DBTYPE, int *));
|
|
int generate_data __P((char [], int, int));
|
|
int insert_btree __P((DB *, DB_ENV *, int, int, int));
|
|
int insert_heap __P((DB *, DB_ENV *, int, int, int));
|
|
int open_db __P((
|
|
DB **, DB_ENV *, DBTYPE, char *, u_int32_t, u_int32_t, u_int32_t));
|
|
int open_env __P((DB_ENV **, char *, u_int32_t));
|
|
int run_workload __P((DB *, int, int, int));
|
|
void usage __P((void));
|
|
|
|
const char *progname = "ex_heap";
|
|
|
|
int
|
|
main(argc, argv)
|
|
int argc;
|
|
char *argv[];
|
|
{
|
|
extern char *optarg;
|
|
DB_ENV *dbenv;
|
|
DB *dbp;
|
|
u_int32_t cachesize, ghpsize, hpsize, pgsize;
|
|
char *home;
|
|
int ch, ret, set_ghpsize, set_hpsize, test_btree, test_var_data;
|
|
int recs_per_rep, repeats;
|
|
|
|
dbenv = NULL;
|
|
dbp = NULL;
|
|
cachesize = 0;
|
|
ret = set_ghpsize = set_hpsize = test_btree = 0;
|
|
home = NULL;
|
|
|
|
recs_per_rep = DEF_RECS_PER_REP;
|
|
ghpsize = hpsize = pgsize = 0;
|
|
repeats = DEF_REPEATS;
|
|
test_var_data = 0; /* Default as fix-length data. */
|
|
|
|
while ((ch = getopt(argc, argv, "bc:dh:n:p:r:S:s:")) != EOF)
|
|
switch (ch) {
|
|
case 'b':
|
|
test_btree = 1;
|
|
break;
|
|
case 'c':
|
|
cachesize = atoi(optarg);
|
|
break;
|
|
case 'd':
|
|
test_var_data = 1;
|
|
break;
|
|
case 'h':
|
|
home = optarg;
|
|
break;
|
|
case 'n':
|
|
recs_per_rep = atoi(optarg);
|
|
break;
|
|
case 'p':
|
|
pgsize = atoi(optarg);
|
|
break;
|
|
case 'r':
|
|
repeats = atoi(optarg);
|
|
break;
|
|
case 's':
|
|
set_hpsize = 1;
|
|
hpsize = atoi(optarg);
|
|
break;
|
|
case 'S':
|
|
set_ghpsize = 1;
|
|
ghpsize = atoi(optarg);
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
|
|
if (!home)
|
|
usage();
|
|
|
|
srand((int)time(NULL));
|
|
|
|
/*
|
|
* If heap size is not specified, then use our default configuration
|
|
* as follows.
|
|
*/
|
|
if (!set_hpsize && !set_ghpsize)
|
|
hpsize = AVG_SPACE_PER_RECORD * (DEF_INIT_RECS + recs_per_rep);
|
|
|
|
if ((ret = open_env(&dbenv, home, cachesize)) != 0) {
|
|
fprintf(stderr, "%s: open_env: %s", progname, db_strerror(ret));
|
|
goto err;
|
|
}
|
|
|
|
if ((ret = open_db(&dbp, dbenv, DB_HEAP, home,
|
|
ghpsize, hpsize, pgsize)) != 0) {
|
|
dbenv->err(dbenv, ret, "Failed to open heap database.");
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* Perform requested rounds of insert/delete operations
|
|
* using heap database.
|
|
*/
|
|
if ((ret =
|
|
run_workload(dbp, repeats, recs_per_rep, test_var_data)) != 0) {
|
|
dbenv->err(dbenv, ret,
|
|
"Failed to perform operations on heap database.");
|
|
goto err;
|
|
}
|
|
|
|
if (test_btree) {
|
|
/* Close the DB handle for heap. */
|
|
if ((ret = dbp->close(dbp, 0)) != 0) {
|
|
dbenv->err(dbenv, ret, "DB->close");
|
|
goto err;
|
|
}
|
|
dbp = NULL;
|
|
|
|
if ((ret =
|
|
open_db(&dbp, dbenv, DB_BTREE, home, 0, 0, pgsize)) != 0) {
|
|
dbenv->err(dbenv, ret,
|
|
"Failed to open btree database.");
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* Perform requested rounds of insert/delete operations
|
|
* using btree database.
|
|
*/
|
|
if ((ret = run_workload(dbp,
|
|
repeats, recs_per_rep, test_var_data)) != 0) {
|
|
dbenv->err(dbenv, ret,
|
|
"Failed to perform operations on btree database.");
|
|
goto err;
|
|
}
|
|
}
|
|
err:
|
|
if (dbp != NULL && (ret = dbp->close(dbp, 0)) != 0)
|
|
dbenv->err(dbenv, ret, "DB->close");
|
|
dbp = NULL;
|
|
|
|
if (dbenv != NULL && (ret = dbenv->close(dbenv, 0)) != 0) {
|
|
fprintf(stderr, "%s: dbenv->close: %s", progname,
|
|
db_strerror(ret));
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
run_workload(dbp, repeats, recs_per_rep, test_var)
|
|
DB *dbp;
|
|
int repeats, recs_per_rep, test_var;
|
|
{
|
|
DB_ENV *dbenv;
|
|
DBTYPE dbtype;
|
|
u_int32_t ghpsize, hpsize;
|
|
struct timeval end_time, start_time;
|
|
double *time_secs;
|
|
int *db_file_sizes, fsize, i, ret;
|
|
|
|
dbenv = dbp->dbenv;
|
|
fsize = 0;
|
|
time_secs = NULL;
|
|
db_file_sizes = NULL;
|
|
|
|
if ((ret = dbp->get_type(dbp, &dbtype)) != 0) {
|
|
dbenv->err(dbenv, ret, "DB->get_type");
|
|
goto err;
|
|
}
|
|
|
|
if (dbtype == DB_HEAP &&
|
|
(ret = dbp->get_heapsize(dbp, &ghpsize, &hpsize)) != 0) {
|
|
dbenv->err(dbenv, ret, "DB->get_heapsize");
|
|
goto err;
|
|
}
|
|
|
|
/* An array to record the physical database file size. */
|
|
if ((db_file_sizes =
|
|
(int *)malloc((repeats + 1) * sizeof(int))) == NULL) {
|
|
fprintf(stderr,
|
|
"%s: Unable to allocate space for array db_file_sizes.\n",
|
|
progname);
|
|
goto err;
|
|
}
|
|
memset(db_file_sizes, 0, (repeats + 1) * sizeof(int));
|
|
|
|
/* An array to record the running time for each repetition. */
|
|
if ((time_secs =
|
|
(double *)malloc((repeats + 1) * sizeof(double))) == NULL) {
|
|
fprintf(stderr,
|
|
"%s: Unable to allocate space for array time_secs.\n",
|
|
progname);
|
|
goto err;
|
|
}
|
|
memset(time_secs, 0, (repeats + 1) * sizeof(double));
|
|
|
|
printf("\n\n======================================================");
|
|
printf("\nAbout to enter the insert phase.");
|
|
printf("\n\tDatabase type: %s \t",
|
|
dbtype == DB_HEAP ? "Heap" : "Btree");
|
|
if (dbtype == DB_HEAP)
|
|
printf("with configured heapsize = %d gbytes and %d bytes.",
|
|
ghpsize, hpsize);
|
|
printf("\n\tPagesize: %d", dbp->pgsize);
|
|
printf("\n\tInitial records number: %d", DEF_INIT_RECS);
|
|
printf("\n\tNumber of repetitions: %d", repeats);
|
|
printf("\n\tNumber of inserts per repetition: %d\n", recs_per_rep);
|
|
|
|
/*
|
|
* Insert records to the database and delete the same number from
|
|
* the database, then check the change of the physical database file.
|
|
*
|
|
* Don't delete after the first insertion to leave some data
|
|
* in the tables for subsequent iterations.
|
|
*/
|
|
for (i = 0; i <= repeats; i++) {
|
|
/* Time for each loop. */
|
|
(void)gettimeofday(&start_time, NULL);
|
|
|
|
if ((dbtype == DB_HEAP) && (ret = insert_heap(dbp, dbenv,
|
|
i == 0 ? DEF_INIT_RECS : recs_per_rep,
|
|
i == 0 ? 0 : (DEF_INIT_RECS + (i - 1) * recs_per_rep),
|
|
test_var)) != 0) {
|
|
dbenv->err(dbenv, ret,
|
|
"Failed to insert records to heap database.");
|
|
goto err;
|
|
}
|
|
|
|
if ((dbtype == DB_BTREE) && (ret = insert_btree(dbp, dbenv,
|
|
i == 0 ? DEF_INIT_RECS : recs_per_rep,
|
|
i == 0 ? 0 : (DEF_INIT_RECS + (i - 1) * recs_per_rep),
|
|
test_var)) != 0) {
|
|
dbenv->err(dbenv, ret,
|
|
"Failed to insert records to btree database.");
|
|
goto err;
|
|
}
|
|
|
|
if (i > 0 &&
|
|
(ret = delete_recs(dbp, dbenv, recs_per_rep)) != 0) {
|
|
dbenv->err(dbenv, ret, "Failed to delete records.");
|
|
goto err;
|
|
}
|
|
|
|
(void)gettimeofday(&end_time, NULL);
|
|
time_secs[i] =
|
|
(((double)end_time.tv_sec * NS_PER_MS +
|
|
end_time.tv_usec) -
|
|
((double)start_time.tv_sec * NS_PER_MS +
|
|
start_time.tv_usec)) / NS_PER_MS;
|
|
|
|
/* Calculate the physical file size for each repetition. */
|
|
if ((ret = file_size(dbp, dbtype, &fsize)) != 0) {
|
|
dbenv->err(dbenv, ret, "Failed to calculate "
|
|
"the file size on repeat %d.\n", i);
|
|
goto err;
|
|
}
|
|
db_file_sizes[i] = fsize;
|
|
}
|
|
printf("\n------------------------------------------------------\n");
|
|
printf("%5s \t %10s \t %10s\n", "repetition", "physical file size",
|
|
"running time");
|
|
for (i = 0; i <= repeats; i++)
|
|
printf("%5d \t\t %10d \t\t %.2f seconds\n",
|
|
i, db_file_sizes[i], time_secs[i]);
|
|
|
|
err:
|
|
if (db_file_sizes != NULL)
|
|
free(db_file_sizes);
|
|
if (time_secs != NULL)
|
|
free(time_secs);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/* Calculate the size of the given database. */
|
|
int
|
|
file_size(dbp, dbtype, fsize)
|
|
DB *dbp;
|
|
DBTYPE dbtype;
|
|
int *fsize;
|
|
{
|
|
DB_ENV *dbenv;
|
|
u_int32_t pgcnt, pgsize;
|
|
int ret, size;
|
|
void *statp;
|
|
|
|
dbenv = dbp->dbenv;
|
|
pgsize = dbp->pgsize;
|
|
ret = size = 0;
|
|
|
|
if ((ret = dbp->stat(dbp, NULL, &statp, DB_FAST_STAT)) != 0) {
|
|
dbenv->err(dbenv, ret, "DB->stat");
|
|
return (ret);
|
|
}
|
|
|
|
pgcnt = (dbtype == DB_HEAP ? ((DB_HEAP_STAT *)statp)->heap_pagecnt :
|
|
((DB_BTREE_STAT *)statp)->bt_pagecnt);
|
|
|
|
size = pgcnt * pgsize;
|
|
*fsize = size;
|
|
|
|
free(statp);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* Insert an certain number of records to heap database,
|
|
* with the key beginning with a specified value.
|
|
*/
|
|
int
|
|
insert_heap(dbp, dbenv, numrecs, start, test_var)
|
|
DB *dbp;
|
|
DB_ENV *dbenv;
|
|
int numrecs, start, test_var;
|
|
{
|
|
DB_HEAP_RID rid;
|
|
DBT key, data;
|
|
char buf[BUFFER_LEN];
|
|
int cnt, ret;
|
|
|
|
memset(&rid, 0, sizeof(DB_HEAP_RID));
|
|
memset(&key, 0, sizeof(DBT));
|
|
memset(&data, 0, sizeof(DBT));
|
|
|
|
ret = 0;
|
|
|
|
key.data = &rid;
|
|
key.size = key.ulen = sizeof(DB_HEAP_RID);
|
|
key.flags = DB_DBT_USERMEM;
|
|
data.data = buf;
|
|
data.flags = DB_DBT_USERMEM;
|
|
|
|
for (cnt = start; cnt < (numrecs + start) &&
|
|
(ret = generate_data(buf, cnt, test_var)) == 0; ++cnt) {
|
|
data.size = data.ulen = (u_int32_t)strlen(buf) + 1;
|
|
|
|
/* Require DB_APPEND flag to add new data to the database.*/
|
|
if ((ret = dbp->put(dbp, NULL, &key, &data, DB_APPEND)) != 0) {
|
|
dbenv->err(dbenv, ret, "insert_heap:DB->put");
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* Insert an certain number of records to btree database,
|
|
* with the key beginning with a specified value.
|
|
*/
|
|
int
|
|
insert_btree(dbp, dbenv, numrecs, start, test_var)
|
|
DB *dbp;
|
|
DB_ENV *dbenv;
|
|
int numrecs, start, test_var;
|
|
{
|
|
DBT key, data;
|
|
char buf[BUFFER_LEN];
|
|
int cnt, ret;
|
|
|
|
memset(&key, 0, sizeof(DBT));
|
|
memset(&data, 0, sizeof(DBT));
|
|
|
|
ret = 0;
|
|
|
|
key.data = &cnt;
|
|
key.size = key.ulen = sizeof(int);
|
|
key.flags = DB_DBT_USERMEM;
|
|
data.data = buf;
|
|
data.flags = DB_DBT_USERMEM;
|
|
|
|
for (cnt = start; cnt < (numrecs + start) &&
|
|
(ret = generate_data(buf, cnt, test_var)) == 0; ++cnt) {
|
|
data.size = data.ulen = (u_int32_t)strlen(buf) + 1;
|
|
|
|
if ((ret = dbp->put(dbp, NULL, &key, &data, 0)) != 0) {
|
|
dbenv->err(dbenv, ret, "insert_btree:DB->put");
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/* Generate the data for the specified record. */
|
|
int
|
|
generate_data(buf, rec_no, test_var)
|
|
char *buf;
|
|
int rec_no, test_var;
|
|
{
|
|
const char *str = "abcdefghijklmnopqrst";
|
|
size_t len = strlen(str);
|
|
|
|
/*
|
|
* Default use the fix-length data,
|
|
* if required then use variable-length data.
|
|
*/
|
|
if (test_var == 1)
|
|
len = rand() % (len - 2) + 1;
|
|
|
|
(void)sprintf(buf, "%04d_%*s", rec_no, len, str);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* Delete an certain number of records. */
|
|
int
|
|
delete_recs(dbp, dbenv, numrecs)
|
|
DB *dbp;
|
|
DB_ENV *dbenv;
|
|
int numrecs;
|
|
{
|
|
DBC *dbcp;
|
|
DBT key, data;
|
|
int cnt, ret;
|
|
|
|
memset(&key, 0, sizeof(DBT));
|
|
memset(&data, 0, sizeof(DBT));
|
|
|
|
dbcp = NULL;
|
|
cnt = ret = 0;
|
|
|
|
/*
|
|
* Delete from the first entry, get the first entry using
|
|
* the DBcursor, then delete it using the DB handle.
|
|
*/
|
|
if ((ret = dbp->cursor(dbp, NULL, &dbcp, 0)) != 0) {
|
|
dbenv->err(dbenv, ret, "delete_recs:DB->cursor");
|
|
goto err;
|
|
}
|
|
|
|
while ((ret = dbcp->get(dbcp, &key, &data, DB_NEXT)) == 0 &&
|
|
cnt < numrecs) {
|
|
if ((ret = dbcp->del(dbcp, 0)) != 0) {
|
|
dbenv->err(dbenv, ret, "delete_recs:DBCursor->del");
|
|
break;
|
|
} else
|
|
++cnt;
|
|
}
|
|
|
|
err:
|
|
if (dbcp != NULL && (ret = dbcp->close(dbcp)) != 0)
|
|
dbenv->err(dbenv, ret, "delete_recs:DBCursor->close");
|
|
|
|
return (ret);
|
|
}
|
|
|
|
void
|
|
usage()
|
|
{
|
|
fprintf(stderr, "usage: %s:\n%s \n %s\n", progname,
|
|
"\t[-b][-c cachesize][-d] -h home [-n recs_per_rep]",
|
|
"\t[-p pgsize][-r repeats][-S ghpsize][-s hpsize]");
|
|
|
|
fprintf(stderr, "-b: run sample application using a btree database.\n");
|
|
fprintf(stderr, "-c: specify the cache size for the environment.\n");
|
|
fprintf(stderr, "-d: test on variable-length data "
|
|
"(default: fix-length).\n");
|
|
fprintf(stderr, "-h: specify the home directory for "
|
|
"the environment (required).\n");
|
|
fprintf(stderr, "-n: specify the num. of records "
|
|
"per repetition (default: %d).\n", DEF_RECS_PER_REP);
|
|
fprintf(stderr, "-p: specify the pgsize of database.\n");
|
|
fprintf(stderr, "-r: number of repetition (a pair of "
|
|
"insertion and deletion (default: %d)).\n", DEF_REPEATS);
|
|
fprintf(stderr,
|
|
"-S: specify the heap size (gbytes) for the heap database.\n");
|
|
fprintf(stderr,
|
|
"-s: specify the heap size (bytes) for the heap database.\n");
|
|
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
int
|
|
open_env(dbenvp, home, cachesize)
|
|
DB_ENV **dbenvp;
|
|
char *home;
|
|
u_int32_t cachesize;
|
|
{
|
|
DB_ENV *dbenv;
|
|
int ret = 0;
|
|
|
|
/* Create an environment handle and open an environment. */
|
|
if ((ret = db_env_create(&dbenv, 0)) != 0) {
|
|
fprintf(stderr, "%s: db_env_create: %s\n",
|
|
progname, db_strerror(ret));
|
|
return (ret);
|
|
}
|
|
|
|
*dbenvp = dbenv;
|
|
|
|
dbenv->set_errfile(dbenv, stderr);
|
|
dbenv->set_errpfx(dbenv, progname);
|
|
|
|
if ((cachesize > 0) && (ret =
|
|
dbenv->set_cachesize(dbenv, (u_int32_t)0, cachesize, 1)) != 0) {
|
|
dbenv->err(dbenv, ret, "DB_ENV->set_cachesize");
|
|
return (ret);
|
|
}
|
|
|
|
if ((ret = dbenv->open(dbenv, home, DB_CREATE | DB_INIT_MPOOL, 0)) != 0)
|
|
dbenv->err(dbenv, ret, "DB_ENV->open");
|
|
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
open_db(dbpp, dbenv, dbtype, home, ghpsize, hpsize, pgsize)
|
|
DB **dbpp;
|
|
DB_ENV *dbenv;
|
|
DBTYPE dbtype;
|
|
char *home;
|
|
u_int32_t ghpsize, hpsize, pgsize;
|
|
{
|
|
DB *dbp;
|
|
u_int32_t dbflags = 0;
|
|
char *dbname;
|
|
int ret = 0;
|
|
|
|
dbname = (dbtype == DB_HEAP) ? "heap.db" : "btree.db";
|
|
|
|
/* Create a database handle and open a database. */
|
|
if ((ret = db_create(&dbp, dbenv, 0)) != 0) {
|
|
dbenv->err(dbenv, ret, "db_create : %s", dbname);
|
|
goto err;
|
|
}
|
|
|
|
*dbpp = dbp;
|
|
|
|
if ((dbtype == DB_BTREE) &&
|
|
(ret = dbp->set_bt_compare(dbp, compare_int)) != 0) {
|
|
dbp->err(dbp, ret, "DB->set_bt_compare");
|
|
goto err;
|
|
}
|
|
|
|
if ((dbtype == DB_HEAP) && (ghpsize > 0 || hpsize > 0) &&
|
|
(ret = dbp->set_heapsize(dbp, ghpsize, hpsize, 0)) != 0) {
|
|
dbenv->err(dbenv, ret, "DB->set_heapsize");
|
|
return (ret);
|
|
}
|
|
|
|
if ((pgsize > 0) && (ret = dbp->set_pagesize(dbp, pgsize)) != 0) {
|
|
dbenv->err(dbenv, ret, "DB->set_pagesize");
|
|
return (ret);
|
|
}
|
|
|
|
if ((ret =
|
|
dbp->open(dbp, NULL, dbname, NULL, dbtype, DB_CREATE, 0)) != 0)
|
|
dbenv->err(dbenv, ret, "DB->open");
|
|
err:
|
|
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
compare_int(dbp, a, b)
|
|
DB *dbp;
|
|
const DBT *a, *b;
|
|
{
|
|
int ai, bi;
|
|
|
|
dbp = NULL;
|
|
|
|
/*
|
|
* Returns:
|
|
* < 0 if a < b
|
|
* = 0 if a = b
|
|
* > 0 if a > b
|
|
*/
|
|
memcpy(&ai, a->data, sizeof(int));
|
|
memcpy(&bi, b->data, sizeof(int));
|
|
return (ai - bi);
|
|
}
|