我已经完成了拼写测试并通过了所有的检查。但我还是对你的表演感到不安。我尽了最大的努力进行研究和测试,但我的实现比员工的慢10-20%。如何改进我的代码?
// Implements a dictionary's functionality
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "dictionary.h"
// Represents a node in a hash table
typedef struct node
{
char word[LENGTH + 1];
struct node *next;
}
node;
//Prototypes
unsigned int hash(const char *word);
// TODO: Choose number of buckets in hash table
const unsigned int N = 187751; //odd prime number bigger than word count, because math
// Hash table
node *table[N]; //may need to use calloc
//word count
int word_count = 0;
// Returns true if word is in dictionary, else false
bool check(const char *word)
{
// TODO
//debug
//int hashcode = hash(word);
//printf("bucket: %i, word: %s\n", hash(word), table[hash(word)]->word);
for (node *tmp = table[hash(word)]; tmp != NULL; tmp = tmp->next) //iterate through bucket
{
if (strcasecmp(tmp->word, word) == 0) //compare strings
{
return true;
}
}
return false;
}
// Hashes word to a number
unsigned int hash(const char *word)
{
// TODO: Improve this hash function
//Used Data Structures youtube course of Dr. Rob Edwards from San Diego State University mostly
int hash = 0;
/*int w = 0;
//convert to upper case
while (word[w] != '\0')
{
word[w] = toupper(word[w]);
w++;
}*/
for (int i = 0; i <= strlen(word); i++)
{
hash = (31 * hash + toupper(word[i])) % N; //31 because math
//check hash size
/*if (hash >= N)
{
printf("incorrect hash!");
}*/
}
return hash;
}
// Loads dictionary into memory, returning true if successful, else false
bool load(const char *dictionary)
{
// TODO
//open dictionary file
FILE *dict = fopen(dictionary, "r");
if (dict == NULL)
{
//printf("Could not open file.\n");
return false;
}
//create temp string for word
char *word = malloc(LENGTH + 1);
if (word == NULL)
{
return false;
}
//read words from dictionary and write to hash table
while (fscanf(dict, "%s", word) != EOF)
{
//node *tmp_word = NULL;
node *n = malloc(sizeof(node));
if (n == NULL)
{
return false;
}
int pos = hash(word); //run hash to find the bucket in table
strcpy(n->word, word); //write word to temp node
if (table[pos] == NULL) //check if node is first in bucket (may need to use calloc to create a table)
{
n->next = NULL;
}
else //if not the first, write node to the head
{
n->next = table[pos]; //reference first node
}
table[pos] = n; //write node to table
word_count++; //count new word
}
fclose(dict);
free(word);
//debug
/*int j = 0;
while (j <= 11)
{
for (node *tmp = table[j]; tmp != NULL; tmp = tmp->next) //делаем ноду; пока не дойдем до последней; переходим к следующей
{
printf("%s\n", tmp->word); //забираем значение ноды
}
printf("Bucket number: %i\n", j);
j++;
}*/
//printf("word count:%i\n", word_count);
return true;
}
// Returns number of words in dictionary if loaded, else 0 if not yet loaded
unsigned int size(void)
{
return word_count;
}
// Unloads dictionary from memory, returning true if successful, else false
bool unload(void)
{
// TODO
for (int i = 0; i <= N; i++)
{
while (table[i] != NULL)
{
node *tmp = table[i]->next; //делаем ноду и переходим на следующую ячейку
free(table[i]); //освобождаем текущую
table[i] = tmp; //начинаем list со второй ячейки
}
}
return true;
}
我的成果:
WORDS MISSPELLED: 955
WORDS IN DICTIONARY: 143091
WORDS IN TEXT: 17756
TIME IN load: 0.04
TIME IN check: 0.02
TIME IN size: 0.00
TIME IN unload: 0.01
TIME IN TOTAL: 0.07
CS50工作人员的结果:
WORDS MISSPELLED: 955
WORDS IN DICTIONARY: 143091
WORDS IN TEXT: 17756
TIME IN load: 0.02
TIME IN check: 0.02
TIME IN size: 0.00
TIME IN unload: 0.01
TIME IN TOTAL: 0.06
我已经玩过水桶大小,无济于事((
Pset 5说明:https://cs50.harvard.edu/x/2022/psets/5/speller/
我还包括dictionary.h,如果你需要的话。Check正在从另一个文本文件中获取字符串,而不是从加载中使用的文本文件中获取字符串。
#ifndef DICTIONARY_H
#define DICTIONARY_H
#include <stdbool.h>
// Maximum length for a word
// (e.g., pneumonoultramicroscopicsilicovolcanoconiosis)
#define LENGTH 45
// Prototypes
bool check(const char *word);
unsigned int hash(const char *word);
bool load(const char *dictionary);
unsigned int size(void);
bool unload(void);
#endif // DICTIONARY_H
我的问题在Craig Estey中得到了完美的回答。他的代码运行速度比我的快,比cs50员工的更快:
较大的文本:
我学到了很多,我真的很棒。这是我在这里的第一个问题,在SO,我从来没有预料到它是如此乐于助人和友好的地方。
3条答案
按热度按时间apeeds0o1#
几个问题:
while (fscanf(dict, "%s", word) != EOF)
错误。您想要:while (fscanf(dict, "%s", word) == 1)
1.更快地将给定的单词 * 存储 * 为 * 大写 *。(即)我们对每个单词只执行
toupper
* 一次 *。check
函数将更快,因为它可以使用strcmp
而不是strcasecmp
[这是 * 慢 *]1.如果我们可以将字段添加到
node
结构体中,我们就可以让它保存一个哈希值。这可以加快check
函数的速度(请参阅下面代码中的NODEHASH
)。1.为 each 节点执行
malloc
是缓慢/浪费的。pooled/竞技场/slab分配器可以更快(请参阅下面代码中的FASTALLOC
)。同样,if 我们可以向node
结构体添加一个字段。下面的代码中还记录了一些其他错误(请参阅“注意”注解)。我使用了
cpp
条件来表示旧的与新代码:下面是重构后的代码。我已经编译了它,但没有测试它:
下面是上面的一个清理版本(我通过
unifdef
运行它):更新#2:
我已经提交了我的版本。只是想更好地理解这个主题。- 梅奥特
好的,正因为如此,我已经制作了一个最终版本,这是我为速度所做的最大努力。如果你还在做的话,我会做更多的重构。
注意事项...
为每个节点执行 single
malloc
是浪费的。malloc
相对较慢。请注意,在您的基准测试时间中,主要是load
阶段比staff实现慢。此外,
malloc
在内存中放置了一个 hidden 结构体,该结构体紧接在它返回的指针之前,用于跟踪分配。此结构占用空间,对于像节点结构这样的小分配,额外的内存开销可能很大。最好调用
malloc
并在一个连续的块或数组中分配大量的node
结构。进一步的分配可以来自该块内。这类似于“slab allocation”、“object pools”或“memory pools”:在下面的代码中,节点以1000为单位进行分配。这将
malloc
调用的数量从100,000减少到大约100。这个版本比我的上一个分配器提高了速度,因为它使用了一个额外的“段”(又名“竞技场”)结构来控制分配。它不再需要将每个节点结构体预链接到一个“自由列表”中,或者跟踪一个节点是否是从malloc
接收的给定内存中的第一个节点。阅读字典时无需执行
toupper/tolower
。因为字典保证全部小写,每行只有一个单词,所以使用fgets
读取字典会更快。我们可以将字典单词 * 直接 * 读入给定node
结构体的word
字段[* 如果 * 我们在执行fgets
之前执行节点分配]。在计算哈希值时,我们不需要在每次迭代中都执行
% N
。由于模算术的性质,我们可以在最后做一次余数运算。我们还可以将
hash
值存储到node
结构中。然后,在check
中,我们可以比较哈希值并跳过一些[昂贵的]strcmp
调用。在您的代码中,由于N
非常大,大多数哈希桶只有一个或两个条目,因此这种加速效果可能很小[并且可能会稍微慢一些]。在
check
中,最好将参数word
复制到一个临时缓冲区中,使用小写 once。然后,我们可以使用更快的strcmp
而不是更慢的strcasecmp
。无论我们是否将哈希值存储在节点结构中,这都有效。同样,由于N
值很大,这种加速效果可能有限。下面是重构后的代码:
eni9jsuy2#
不确定这是否仍然是一个问题,但大部分性能都来自哈希函数。
而不是调用像toUpper toLower这样的持久字符串函数,你应该依赖于一个快速简单的哈希函数:
N为65535时工作正常
r8uurelv3#
这里有另一个更简单的方法来大大减少总时间,你只需要做一个更快的哈希函数。
我在这里使用的N值是786433,这是一个大素数。输出如下所示。